I've some experience using Azure CLI, Az Module and ARM templates... Anyway I'm experimenting problems in setting a storage account to a web app.
This is the bicep source (still a work in progress):
#allowed([
'dev'
'qta'
'ppd'
'prd'
])
param targetEnv string = 'dev'
#allowed([
'southafricanorth'
'southafricawest'
'northeurope'
'westeurope'
'australiaeast'
'australiasoutheast'
'australiacentral'
'australiacentral2'
'eastasia'
'southeastasia'
'brazilsouth'
'brazilsoutheast'
'centralus'
'eastus'
'eastus2'
'westus'
'westus2'
'westus3'
'northcentralus'
'southcentralus'
])
param location string = 'westeurope'
param planName string = 'testplan1'
param planGroup string = 'rgdoftempdev'
var locationMap = {
'southafricanorth': 'af'
'southafricawest': 'af'
'northeurope': 'eu'
'westeurope': 'eu'
'australiaeast': 'pc'
'australiasoutheast': 'pc'
'australiacentral': 'pc'
'australiacentral2': 'pc'
'eastasia': 'as'
'southeastasia': 'as'
'brazilsouth': 'sa'
'brazilsoutheast': 'sa'
'centralus': 'us'
'eastus': 'us'
'eastus2': 'us'
'westus': 'us'
'westus2': 'us'
'westus3': 'us'
'northcentralus': 'us'
'southcentralus': 'us'
}
var locationAcr = locationMap[location]
// var hash = substring(uniqueString(subscription().subscriptionId), 0, 4)
var appName = 'bvdof'
var insightsName = '${appName}-appinsights-${locationAcr}-${targetEnv}'
var storageName = '${appName}sa${locationAcr}${targetEnv}'
var webAppName = '${appName}-webapp-${locationAcr}-${targetEnv}'
resource storageAccount 'Microsoft.Storage/storageAccounts#2021-06-01' = {
name: storageName
location: location
kind: 'StorageV2'
sku: {
name: 'Premium_LRS'
}
properties: {
minimumTlsVersion: 'TLS1_2'
allowBlobPublicAccess: true
networkAcls: {
bypass: 'AzureServices'
defaultAction: 'Allow'
}
supportsHttpsTrafficOnly: true
encryption: {
keySource: 'Microsoft.Storage'
services: {
blob: {
keyType: 'Account'
enabled: true
}
file: {
keyType: 'Account'
enabled: true
}
}
}
accessTier: 'Hot'
}
}
resource appInsights 'Microsoft.Insights/components#2020-02-02' = {
name: insightsName
location: location
kind: 'web'
properties: {
Application_Type: 'web'
publicNetworkAccessForIngestion: 'Enabled'
publicNetworkAccessForQuery: 'Enabled'
}
}
resource webApplication 'Microsoft.Web/sites#2021-02-01' = {
dependsOn: [
appInsights
storageAccount
]
name: webAppName
location: resourceGroup().location
kind: 'app'
properties: {
httpsOnly: true
serverFarmId: '/subscriptions/${subscription().id}/resourceGroups/${planGroup}/providers/Microsoft.Web/serverfarms/${planName}'
clientAffinityEnabled: true
siteConfig: {
appSettings: [
{
name: 'APPINSIGHTS_INSTRUMENTATIONKEY'
value: appInsights.properties.InstrumentationKey
}
// {
// name: 'AzureWebJobsDashboard'
// value: 'DefaultEndpointsProtocol=https;AccountName=${storageAccount.name};EndpointSuffix=${environment().suffixes.storage};AccountKey=${listKeys(storageAccount.id, storageAccount.apiVersion).keys[0].value}'
// }
// {
// name: 'AzureWebJobsStorage'
// value: 'DefaultEndpointsProtocol=https;AccountName=${storageAccount.name};EndpointSuffix=${environment().suffixes.storage};AccountKey=${listKeys(storageAccount.id, storageAccount.apiVersion).keys[0].value}'
// }
{
name: 'WEBSITE_CONTENTAZUREFILECONNECTIONSTRING'
value: 'DefaultEndpointsProtocol=https;AccountName=${storageName};AccountKey=${listKeys(storageAccount.id, storageAccount.apiVersion).keys[0].value}'
}
{
name: 'WEBSITE_CONTENTSHARE'
value: webAppName
}
{
name: 'ANCM_ADDITIONAL_ERROR_PAGE_LINK'
value: 'https://${webAppName}.scm.azurewebsites.net/detectors?type=tools&name=eventviewer'
}
{
name: 'APPINSIGHTS_PROFILERFEATURE_VERSION'
value: '1.0.0'
}
{
name: 'APPINSIGHTS_SNAPSHOTFEATURE_VERSION'
value: '1.0.0'
}
{
name: 'APPLICATIONINSIGHTS_CONNECTION_STRING'
value: 'InstrumentationKey=${appInsights.properties.InstrumentationKey};IngestionEndpoint=https://${location}.in.applicationinsights.azure.com/'
}
]
}
}
}
This is the error I get after partially failed deployment (storage and app insights created):
{"status":"Failed","error":{"code":"DeploymentFailed","message":"At least one resource deployment operation failed. Please list deployment operations for details. Please see https://aka.ms/DeployOperations for usage details.","details":[{"code":"BadRequest","message":"{\r\n \"Code\": \"BadRequest\",\r\n \"Message\": \"There was a conflict. The remote name could not be resolved: 'bvdofsaeudev.file.core.windows.net'\",\r\n \"Target\": null,\r\n \"Details\": [\r\n {\r\n \"Message\": \"There was a conflict. The remote name could not be resolved: 'bvdofsaeudev.file.core.windows.net'\"\r\n },\r\n {\r\n \"Code\": \"BadRequest\"\r\n },\r\n {\r\n \"ErrorEntity\": {\r\n \"ExtendedCode\": \"01020\",\r\n
\"MessageTemplate\": \"There was a conflict. {0}\",\r\n \"Parameters\": [\r\n \"The remote name could not be resolved: 'bvdofsaeudev.file.core.windows.net'\"\r\n ],\r\n \"Code\": \"BadRequest\",\r\n \"Message\": \"There was a conflict. The remote name could not be resolved: 'bvdofsaeudev.file.core.windows.net'\"\r\n }\r\n }\r\n ],\r\n \"Innererror\": null\r\n}"}]}}
What is wrong with this definition?
I tested your code and faced the same error as you can see below:
The error in the code is that you are using a Premium_LRS sku and kind is storageV2 . So , it doesn't create a File service in the Storage account only Blob service . For which reason , app is not able to find the remote name of the storage account file server.
There can be two solutions as below:
Just Changing the Sku name from Premium_LRS to Standard_LRS in the storage account resource as below:
resource storageAccount 'Microsoft.Storage/storageAccounts#2021-06-01' = {
name: storageName
location: location
kind: 'StorageV2'
sku: {
name: 'Standard_LRS'
}
properties: {
minimumTlsVersion: 'TLS1_2'
allowBlobPublicAccess: true
networkAcls: {
bypass: 'AzureServices'
defaultAction: 'Allow'
}
supportsHttpsTrafficOnly: true
encryption: {
keySource: 'Microsoft.Storage'
services: {
blob: {
keyType: 'Account'
enabled: true
}
file: {
keyType: 'Account'
enabled: true
}
}
}
accessTier: 'Hot'
}
}
Output:
If you want to use Premium_LRS then Change the Kind to FileStorage instead of StorageV2 as below, so that it create a premium storage account with File service and not Blob service:
resource storageAccount 'Microsoft.Storage/storageAccounts#2021-06-01' = {
name: storageName
location: location
kind: 'FileStorage'
sku: {
name: 'Premium_LRS'
}
properties: {
minimumTlsVersion: 'TLS1_2'
allowBlobPublicAccess: true
networkAcls: {
bypass: 'AzureServices'
defaultAction: 'Allow'
}
supportsHttpsTrafficOnly: true
encryption: {
keySource: 'Microsoft.Storage'
services: {
blob: {
keyType: 'Account'
enabled: true
}
file: {
keyType: 'Account'
enabled: true
}
}
}
accessTier: 'Hot'
}
}
Outputs:
Related
I am trying to get a logic app to read messages from a service bus topic. I am getting the error with the connection in logic app designer as :
Could not retrieve values. Error code: 'Unauthorized', Message: 'The remote server returned an error: (401) Unauthorized. Manage,EntityRead claims required for this operation. TrackingId:, SystemTracker:, Timestamp: clientRequestId: '. More diagnostic information: x-ms-client-request-id is ''.
currently I have tried the following in my cicd pipeline:
Grant permission to the logic app standard to access the connection api
resource servicebusConnectorAccessPolicy 'Microsoft.Web/connections/accessPolicies#2018-07-01-preview' = {
name: '${servicebusConnector.name}/${logicAppName}'
location: location
properties: {
principal: {
type: 'ActiveDirectory'
identity: {
tenantId: subscription().tenantId
objectId: logicApp.identity.principalId
}
}
}
}
Get this error:
ERROR: {"status":"Failed","error":{"code":"DeploymentFailed","message":"At least one resource deployment operation failed. Please list deployment operations for details. Please see https://aka.ms/DeployOperations for usage details.","details":[{"code":"BadRequest","message":"{\r\n "error": {\r\n "code": "InvalidTemplate",\r\n "message": "Unable to process template language expressions for resource '/subscriptions//resourceGroups//providers/Microsoft.Web/connections/servicebus/accessPolicies/' at line '124' and column '5'. 'The language expression property 'principalId' doesn't exist, available properties are 'type, userAssignedIdentities'.'",\r\n "additionalInfo": [\r\n {\r\n "type": "TemplateViolation",\r\n "info": {\r\n "lineNumber": 124,\r\n "linePosition": 5,\r\n "path": ""\r\n }\r\n }\r\n ]\r\n }\r\n}"}]}}
also:
resource logicAppServiceBusRoleAssignment 'Microsoft.Authorization/roleAssignments#2020-10-01-preview' = {
scope: serviceBusNamespace
name: guid('${serviceBusNamespaceName}-${roleDefinitionId}')
properties: {
principalType: 'ServicePrincipal'
roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', roleDefinitionId)
principalId: logicApp.identity.principalId
}
}
which yields this error:
ERROR: {"code": "InvalidTemplateDeployment", "message": "The template deployment failed with error: 'Authorization failed for template resource '' of type 'Microsoft.Authorization/roleAssignments'. The client '' with object id '' does not have permission to perform action 'Microsoft.Authorization/roleAssignments/write' at scope '/subscriptions//resourceGroups//providers/Microsoft.ServiceBus/namespaces//providers/Microsoft.Authorization/roleAssignments/'.'."}
for the role, it lacks permissions to add the role.
my logic app bicep file is as follows:
param logicAppName string // The name of the logic app to create.
param location string //Location for all resources.
param workflowDefinition object // the workflow schema in json
param connectorName string
param serviceBusNamespaceName string
resource managedIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities#2022-01-31-preview' = {
name: <managedIDname>
location: location
}
resource servicebusConnector 'Microsoft.Web/connections#2016-06-01' = {
name: connectorName
location: location
kind: 'V2'
properties: {
api: {
id: concat('/subscriptions/', subscription().subscriptionId, '/providers/Microsoft.Web/locations/', resourceGroup().location, '/managedApis/servicebus')
}
displayName: connectorName
parameterValueSet: {
name: 'managedIdentityAuth'
values: {
namespaceEndpoint: {
value: '<endpoint>'
}
}
}
}
}
resource logicApp 'Microsoft.Logic/workflows#2019-05-01' = {
name: logicAppName
location: location
identity: {
type: 'UserAssigned'
userAssignedIdentities: {
'${managedIdentity.id}': {}
}
}
tags: {
displayName: logicAppName
}
properties: {
state: 'Enabled'
definition: workflowDefinition.definition
parameters: workflowDefinition.parameters
}
}
and for the service bus I have the following code:
param serviceBusNamespaceName string //Name of the Service Bus namespace
param serviceBusQueueName string // Name of the Queue
param location string // Location for all resources.
param topicApprovalName string // name of the topic for approval
param topicApprovalSubscriptionName string // name of the topic subscription
param connectorName string // name of the connector
param logicAppName string
param roleDefinitionId string = '8e3af657-a8ff-443c-a75c-2fe8c4bcb635'
resource logicApp 'Microsoft.Logic/workflows#2019-05-01' existing = {
name: logicAppName
}
resource lamanagedidentity 'Microsoft.ManagedIdentity/userAssignedIdentities#2022-01-31-preview' existing = {
name: 'managedIdentity'
}
resource serviceBusNamespace 'Microsoft.ServiceBus/namespaces#2022-01-01-preview' = {
name: serviceBusNamespaceName
location: location
sku: {
name: 'Standard'
}
identity: {
type: 'UserAssigned'
userAssignedIdentities: {
'${lamanagedidentity.id}': {}
}
}
properties: {}
}
resource serviceBusQueue 'Microsoft.ServiceBus/namespaces/queues#2022-01-01-preview' = {
parent: serviceBusNamespace
name: serviceBusQueueName
properties: {
lockDuration: 'PT5M'
maxSizeInMegabytes: 1024
requiresDuplicateDetection: false
requiresSession: false
defaultMessageTimeToLive: 'P10675199DT2H48M5.4775807S'
deadLetteringOnMessageExpiration: false
duplicateDetectionHistoryTimeWindow: 'PT10M'
maxDeliveryCount: 10
autoDeleteOnIdle: 'P10675199DT2H48M5.4775807S'
enablePartitioning: false
enableExpress: false
}
}
resource topic 'Microsoft.ServiceBus/namespaces/topics#2021-11-01' = {
name: topicApprovalName
parent: serviceBusNamespace
}
resource topicsubscription 'Microsoft.ServiceBus/namespaces/topics/subscriptions#2022-01-01-preview' = {
name: topicApprovalSubscriptionName
parent: topic
}
resource sbNamespaceAuthRules 'Microsoft.ServiceBus/namespaces/AuthorizationRules#2022-01-01-preview' = {
name: <auth name>
parent: serviceBusNamespace
properties: {
rights: [
'Listen'
'Manage'
'Send'
]
}
}
resource servicebusConnector 'Microsoft.Web/connections#2018-07-01-preview' = {
name: connectorName
location: location
kind: 'V2'
properties: {
api: {
id: '/subscriptions/${subscription().subscriptionId}/providers/Microsoft.Web/locations/${location}/managedApis/servicebus'
}
displayName: connectorName
parameterValueSet: {
name: <name>
values: {
namespaceEndpoint: {
value: '<endpoint>'
}
}
}
}
}
// Grant permission to the logic app standard to access the connection api
resource servicebusConnectorAccessPolicy 'Microsoft.Web/connections/accessPolicies#2018-07-01-preview' = {
name: '${servicebusConnector.name}/${logicAppName}'
location: location
properties: {
principal: {
type: 'ActiveDirectory'
identity: {
tenantId: subscription().tenantId
objectId: logicApp.identity.principalId
}
}
}
}
//output connectionRuntimeUrl string = reference(servicebusConnector.id, servicebusConnector.apiVersion, 'full').properties.connectionRuntimeUrl
Not sure why this isn't working... the issue that I'm landing on is the object id of this:
resource servicebusConnectorAccessPolicy 'Microsoft.Web/connections/accessPolicies#2018-07-01-preview' = {
name: '${servicebusConnector.name}/${logicAppName}'
location: location
properties: {
principal: {
type: 'ActiveDirectory'
identity: {
tenantId: subscription().tenantId
objectId: logicApp.identity.principalId
}
}
}
}
any help or advice would be welcome, I've been bashing my head against this for three days, and it just won't work.
thank you very much
I tried using a keyvault and connecting with a primary connection string, this did not work.
I have tried managed identity and roles, but both are not working for me.
it can find the connection, but gives an access error as cited above.
I was expecting for the connection to be authorized and for there to not be an error with the access policy.
I encountered this error in the bicep file while creating function app with VNET integration. My Vnet is in another resource group named 'tst-vnet' separate from my function app. Is there a way to resolve this? Here is the code snippet:
resource functionApp 'Microsoft.Web/sites#2022-03-01' = {
name: functionAppName
location: location
tags: tags
kind: 'functionapp'
identity: {
type: 'SystemAssigned'
}
properties: {
httpsOnly: true
serverFarmId: functionAppHostingPlan.id
clientAffinityEnabled: true
publicNetworkAccess: 'Disabled'
siteConfig: {
appSettings: [
{
name: 'AzureWebJobsStorage'
value: 'DefaultEndpointsProtocol=https;AccountName=${functionAppStorageAccount.name};EndpointSuffix=${environment().suffixes.storage};AccountKey=${listKeys(functionAppStorageAccount.id, functionAppStorageAccount.apiVersion).keys[0].value}'
}
{
name: 'FUNCTIONS_EXTENSION_VERSION'
value: runtimeStackVersion
}
{
name: 'FUNCTIONS_WORKER_RUNTIME'
value: runtimeStack
}
{
name: 'WEBSITE_CONTENTAZUREFILECONNECTIONSTRING'
value: 'DefaultEndpointsProtocol=https;AccountName=${functionAppStorageAccount.name};EndpointSuffix=${environment().suffixes.storage};AccountKey=${listKeys(functionAppStorageAccount.id, functionAppStorageAccount.apiVersion).keys[0].value}'
}
{
name: 'WEBSITE_CONTENTSHARE'
value: 'functionapp'
}
{
name: 'WEBSITE_VNET_ROUTE_ALL'
value: '1'
}
{
name: 'WEBSITE_DNS_SERVER'
value: '168.63.129.16'
}
{
name: 'WEBSITE_CONTENTOVERVNET'
value: '1'
}
]
minTlsVersion: '1.2'
linuxFxVersion: linuxFxVersion
ftpsState: 'FtpsOnly'
}
}
}
I created a module to in order to use a resource group scope in order to reference the 'tst-vnet'
module networkConfig 'modules/network-config.bicep' = {
name: '${deploymentPrefix}-fn-networkcfg'
dependsOn: [
functionApp
]
scope: resourceGroup(pvtResourceGroupName)
params: {
functionAppName: functionAppName
privateBackendSubnet: privateBackendSubnet
privateEndpointVNet: privateEndpointVNet
privateBackendSubnetCIDR: privateBackendSubnetCIDR
}
}
This is the content of network-config.bicep
resource networkConfig 'Microsoft.Web/sites/networkConfig#2022-03-01' = {
name: '${functionAppName}/virtualNetwork'
properties: {
subnetResourceId: resourceId('Microsoft.Network/virtualNetworks/subnets', privateEndpointVNet, privateBackendSubnet)
swiftSupported: true
}
}
Full error from resource group deployment
{
"status": "Failed",
"error": {
"code": "DeploymentFailed",
"message": "At least one resource deployment operation failed. Please list deployment operations for details. Please see https://aka.ms/DeployOperations for usage details.",
"details": [
{
"code": "NotFound",
"message": "{\r\n "error": {\r\n "code": "ParentResourceNotFound",\r\n "message": "Can not perform requested operation on nested resource. Parent resource 'ase-data-tst-edp-fn-001' not found."\r\n }\r\n}"
}
]
When declaring a module, you can set a scope for the module that is different than the scope for the containing Bicep file. In your scenario, as you have existing virtual network, which is different scope, the virtual network should be declared in separate module. For your reference, you can use below template to deploy the function app integrating with virtual network which is in another resource group.
main.bicep file
#description('Location for all resources except Application Insights.')
param location string = resourceGroup().location
#description('Location for Application Insights.')
param appInsightsLocation string
#description('The language worker runtime to load in the function app.')
#allowed([
'node'
'dotnet'
'java'
])
param runtime string = 'node'
#description('The name of the function app that you wish to create.')
param appName string = 'fnapp${uniqueString(resourceGroup().id)}'
#description('Storage Account type')
#allowed([
'Standard_LRS'
'Standard_GRS'
'Standard_RAGRS'
])
param storageAccountType string = 'Standard_LRS'
#description('The name of the virtual network to be linked.')
param vnetName string
#description('The name of the subnet to be created within the virtual network.')
param subnetName string
#description('The name of the resource group where existing virtual network exists.')
param vnresourceGroup string
var functionAppName = appName
var hostingPlanName = appName
var storageAccountName = '${uniqueString(resourceGroup().id)}azfunctions'
var functionWorkerRuntime = runtime
resource virtualNetwork 'Microsoft.Network/virtualNetworks#2020-06-01' existing = {
name: vnetName
scope: resourceGroup(vnresourceGroup)
}
resource storageAccount 'Microsoft.Storage/storageAccounts#2021-04-01' = {
name: storageAccountName
location: location
sku: {
name: storageAccountType
}
kind: 'StorageV2'
}
resource appInsights 'Microsoft.Insights/components#2020-02-02' = {
name: functionAppName
location: appInsightsLocation
kind: 'web'
properties: {
Application_Type: 'web'
}
}
resource serverFarm 'Microsoft.Web/serverfarms#2020-06-01' = {
name: hostingPlanName
location: location
sku: {
name: 'EP1'
tier: 'ElasticPremium'
}
kind: 'elastic'
properties: {
maximumElasticWorkerCount: 20
}
}
resource function 'Microsoft.Web/sites#2020-06-01' = {
name: functionAppName
location: location
kind: 'functionapp'
properties: {
serverFarmId: serverFarm.id
siteConfig: {
appSettings: [
{
name: 'APPINSIGHTS_INSTRUMENTATIONKEY'
value: appInsights.properties.InstrumentationKey
}
{
name: 'APPLICATIONINSIGHTS_CONNECTION_STRING'
value: 'InstrumentationKey=${appInsights.properties.InstrumentationKey}'
}
{
name: 'AzureWebJobsStorage'
value: 'DefaultEndpointsProtocol=https;AccountName=${storageAccount.name};EndpointSuffix= ${environment().suffixes.storage};AccountKey=${storageAccount.listKeys().keys[0].value}'
}
{
name: 'WEBSITE_CONTENTAZUREFILECONNECTIONSTRING'
value: 'DefaultEndpointsProtocol=https;AccountName=${storageAccountName};EndpointSuffix=${environment().suffixes.storage};AccountKey=${storageAccount.listKeys().keys[0].value};'
}
{
name: 'FUNCTIONS_EXTENSION_VERSION'
value: '~3'
}
{
name: 'FUNCTIONS_WORKER_RUNTIME'
value: functionWorkerRuntime
}
{
name: 'WEBSITE_NODE_DEFAULT_VERSION'
value: '~12'
}
]
}
}
dependsOn: [
virtualNetwork
]
}
resource networkConfig 'Microsoft.Web/sites/networkConfig#2020-06-01' = {
parent: function
name: 'virtualNetwork'
properties: {
subnetResourceId: subnet.outputs.subnetId
swiftSupported: true
}
dependsOn: [
subnet
]
}
module subnet 'modules/subnet.bicep' = {
name: subnetName
scope: resourceGroup(vnresourceGroup)
params: {
subnetName: subnetName
subnetPrefix: '10.2.1.0/24'
vnetName: vnetName
}
}
subnet.bicep
#description('The name of the virtual network to be linked.')
param vnetName string
#description('The name of the subnet to be created within the virtual network.')
param subnetName string
#description('The address Prefix of subnet to be created within the virtual network.')
param subnetPrefix string
param deploySubnet bool = true
resource VNET 'Microsoft.Network/virtualNetworks#2021-02-01' existing = {
name: vnetName
}
resource Subnets 'Microsoft.Network/virtualNetworks/subnets#2020-11-01'= if (deploySubnet) {
name: subnetName
parent: VNET
properties: {
addressPrefix: subnetPrefix
privateEndpointNetworkPolicies: 'Disabled'
privateLinkServiceNetworkPolicies: 'Enabled'
delegations: [
{
name: 'Microsoft.Web.serverFarms'
properties: {
serviceName: 'Microsoft.Web/serverFarms'
}
}
]
}
}
output subnetId string = deploySubnet ? Subnets.id:''
For a project I want to deploy three related resources to Azure through Bicep templates: 1) App Service with System Assigned Managed Identity, 2) Key Vault and 3) Access policy for the App Service (step 1) to the Key Vault (step 2).
The AppService deployment outputs the principalId of the System Assigned Identity which is then later on used when deploying the KeyVaultAccessPolicy.
However, when I run the AZ CLI (az deployment sub create --location WestEurope --template-file ./main.bicep --parameters ./parameters/parameters-dev.json)
to deploy this to Azure I get the following error:
'The language expression property 'outputs' doesn't exist, available properties are 'templateHash, parameters, mode, provisioningState, timestamp, duration, correlationId, providers, dependencies, outputResources'.
Does anyone have an idea why referencing the principalId of the App Service does not work here? Many thanks for any help.
Modules and main.bicep:
main.bicep
module appService 'modules/appService.bicep' = {
name: 'deployAppService'
scope: resourceGroup(appServiceResourceGroup)
params: {
name: appServiceName
location: appServiceLocation
alwaysOn: appServiceAlwaysOn
apimIpAddress: appServiceApimIpAddress
appServicePlanResourceGroup: appServicePlanResourceGroup
appServicePlanName: appServicePlanName
}
}
module keyVault 'modules/keyVault.bicep' = {
name: 'deployKeyVault'
scope: resourceGroup(appServiceResourceGroup)
params: {
name: keyVaultName
dependsOn: [ appService ]
location: appServiceLocation
}
}
module keyVaultAccessPolicy 'modules/keyVaultAccessPolicy.bicep' = {
name: 'deployKeyVaultAccessPolicy'
scope: resourceGroup(appServiceResourceGroup)
params: {
name: '${appServiceName}-ap'
dependsOn: [ keyVault ]
objectId: appService.outputs.appServiceManagedIdentity
}
}
appService.bicep
resource appService 'Microsoft.Web/sites#2020-12-01' = {
name: name
location: location
kind: 'app'
identity: {
type: 'SystemAssigned'
}
properties: {
serverFarmId: '${subscription().id}/resourceGroups/${appServicePlanResourceGroup}/providers/Microsoft.Web/serverfarms/${appServicePlanName}'
enabled: true
}
}
output appServiceManagedIdentity string = appService.identity.principalId
keyVault.bicep
resource keyVault 'Microsoft.KeyVault/vaults#2022-07-01' = {
name: name
location: location
dependsOn: dependsOn
properties: {
enabledForDeployment: true
enabledForTemplateDeployment: true
enabledForDiskEncryption: true
tenantId: subscription().tenantId
accessPolicies: []
sku: {
name: 'standard'
family: 'A'
}
}
}
keyVaultAccessPolicy.bicep
resource keyVaultAccessPolicy 'Microsoft.KeyVault/vaults/accessPolicies#2022-07-01' = {
name: name
dependsOn: dependsOn
properties: {
accessPolicies: [
{
tenantId: subscription().tenantId
objectId: objectId
permissions: {
secrets: [
'get'
]
}
}
]
}
}
Already found the answer:
I missed that modules already contain an dependsOn property and so there is no need to pass the dependencies as param.
The KeyVaultAccessPolicy's name did not contain a reference to the parent resource (KeyVault), see name: '${keyVaultName}/add' below
See working modules and main.bicep below:
main.bicep
module appService 'modules/appService.bicep' = {
name: 'deployAppService'
scope: resourceGroup(appServiceResourceGroup)
params: {
name: appServiceName
location: appServiceLocation
alwaysOn: appServiceAlwaysOn
apimIpAddress: appServiceApimIpAddress
appServicePlanResourceGroup: appServicePlanResourceGroup
appServicePlanName: appServicePlanName
}
}
module keyVault 'modules/keyVault.bicep' = {
scope: resourceGroup(appServiceResourceGroup)
name: 'keyVaultDeploy'
params: {
location: appServiceLocation
name: keyVaultName
}
}
module keyVaultAccessPolicy 'modules/keyVaultAccessPolicy.bicep' = {
scope: resourceGroup(appServiceResourceGroup)
name: 'keyVaultAccessPolicyDeploy'
dependsOn: [
keyVault
]
params: {
keyVaultName: keyVaultName
objectId: appService.outputs.appServiceManagedIdentity
}
}
appService.bicep
resource appService 'Microsoft.Web/sites#2020-12-01' = {
name: name
location: location
kind: 'app'
identity: {
type: 'SystemAssigned'
}
properties: {
// left out
}
}
output appServiceManagedIdentity string = appService.identity.principalId
keyVault.bicep
resource keyVault 'Microsoft.KeyVault/vaults#2022-07-01' = {
name: name
location: location
properties: {
enabledForDeployment: true
enabledForTemplateDeployment: true
enabledForDiskEncryption: true
tenantId: subscription().tenantId
accessPolicies: []
sku: {
name: 'standard'
family: 'A'
}
}
}
keyVaultAccessPolicy.bicep
resource keyVaultAccessPolicy 'Microsoft.KeyVault/vaults/accessPolicies#2022-07-01' = {
name: '${keyVaultName}/add'
properties: {
accessPolicies: [
{
tenantId: subscription().tenantId
objectId: objectId
permissions: {
secrets: [
'get'
]
}
}
]
}
}
Just for a heads up you don't need to pass these stuffs with parameters.
Just use the existing tag with the scope it will be easier to get datafrom any resource.
https://learn.microsoft.com/en-us/azure/azure-resource-manager/bicep/existing-resource#different-scope
I created an Azure Function App by Bicep and tried to get the signalr_extension's value to use in the "upstream" configuration section of a serverless Azure SignalR Service. This is how I try to obtain this value in Bicep:
var signalRKey = listKeys(resourceId('Microsoft.Web/sites/host', funcAppName, 'default'), '2022-03-01').systemkeys.signalr_extension
This is how I configure the signalR service's upstream:
urlTemplate: 'https://${funcAppName}.azurewebsites.net/runtime/webhooks/signalr?code=${signalRKey}'
Running the bicep templates leads to the failure below:
Encountered an error (ServiceUnavailable) from host runtime.
When I remove the {signalRKey} from urlTemplate and replace it with a fictitious hard-coded value, the signalR is provisioned successfully.
The other thing that I noticed was that the singalr_extension key value was not populated after the function app was provisioned.
What am I missing in this exercise?
This feature is not available and feasible in App Service Plans. The signalr_extension must be populated manually after deploying the function app and signalR.
The signalr_extension is only created once you've deployed your function app with a SignalRTrigger function.
You can generate this key upfront if you're deploying the Function App and signalR service at the same time:
param functionAppName string
// Create the function app key for signalR
resource signalRKey 'Microsoft.Web/sites/host/systemkeys#2021-03-01' = {
name: '${functionAppName}/default/signalr_extension'
properties: {
name: 'signalr_extension'
}
}
The ARM API to generate function keys is just pointing to the function app API so it could take some time before it become available (see issue on github).
I managed to get this working consistently by deploying the systemkey and signalr using module.
Also for function app running on linux, the AzureWebJobsStorage setting is mandatory.
functionapp-systemkey.bicep module:
param functionAppName string
param keyName string
resource signalRKey 'Microsoft.Web/sites/host/systemkeys#2021-03-01' = {
name: '${functionAppName}/default/${keyName}'
properties: {
name: keyName
}
}
signalr.bicep module:
param location string = resourceGroup().location
param signalRName string
param functionAppName string
resource signalR 'Microsoft.SignalRService/signalR#2022-02-01' = {
name: signalRName
location: location
sku: {
name: 'Free_F1'
tier: 'Free'
capacity: 1
}
properties: {
features: [
{
flag: 'ServiceMode'
value: 'Serverless'
}
{
flag: 'EnableConnectivityLogs'
value: 'true'
}
]
cors: {
allowedOrigins: [
'*'
]
}
tls: {
clientCertEnabled: false
}
upstream: {
templates: [
{
hubPattern: '*'
eventPattern: '*'
categoryPattern: '*'
auth: {
type: 'None'
}
urlTemplate: 'https://${signalRName}.azurewebsites.net/runtime/webhooks/signalr?code=${listKeys(resourceId('Microsoft.Web/sites/host', functionAppName, 'default'), '2022-03-01').systemkeys.signalr_extension}'
}
]
}
}
}
main.bicep:
param location string = resourceGroup().location
param storageName string
param appServicePlanName string
param functionAppName string
param signalRName string
resource storage 'Microsoft.Storage/storageAccounts#2021-09-01' = {
name: storageName
location: location
kind: 'StorageV2'
sku: {
name: 'Standard_LRS'
}
properties: {
supportsHttpsTrafficOnly: true
minimumTlsVersion: 'TLS1_2'
}
}
resource appServicePlan 'Microsoft.Web/serverfarms#2021-03-01' = {
name: appServicePlanName
location: location
sku: {
name: 'Y1'
tier: 'Dynamic'
size: 'Y1'
family: 'Y'
capacity: 0
}
kind: 'functionapp'
properties: {
perSiteScaling: false
elasticScaleEnabled: false
maximumElasticWorkerCount: 1
isSpot: false
reserved: true
isXenon: false
targetWorkerCount: 0
targetWorkerSizeId: 0
zoneRedundant: false
}
}
resource functionApp 'Microsoft.Web/sites#2021-03-01' = {
name: functionAppName
location: location
kind: 'functionapp,linux'
properties: {
serverFarmId: appServicePlan.id
clientAffinityEnabled: false
clientCertEnabled: false
httpsOnly: true
siteConfig:{
linuxFxVersion: 'DOTNET|6.0'
use32BitWorkerProcess: true
ftpsState: 'FtpsOnly'
cors: {
allowedOrigins: [
'https://portal.azure.com'
]
supportCredentials: false
}
minTlsVersion: '1.2'
appSettings: [
{
name: 'FUNCTIONS_EXTENSION_VERSION'
value: '~4'
}
{
name: 'FUNCTIONS_WORKER_RUNTIME'
value: 'dotnet'
}
{
name: 'AzureWebJobsStorage'
value: 'DefaultEndpointsProtocol=https;AccountName=${storage.name};AccountKey=${listKeys(storage.id, '2019-06-01').keys[0].value};EndpointSuffix=core.windows.net;'
}
]
}
}
}
var signalrKeyName = 'signalr_extension'
module signalrKey 'modules/functionapp-systemkey.bicep' = {
name: '${functionAppName}-systemkey-${signalrKeyName}'
params: {
functionAppName: functionApp.name
keyName: signalrKeyName
}
}
module signalr 'modules/signalr.bicep' = {
name: signalRName
params: {
location: location
functionAppName: functionApp.name
signalRName: signalRName
}
dependsOn:[
signalrKey
]
}
I am looking for solution where I can create multiple storage accounts and create child resources like blob for each storage accounts using loops in Bicep.I was able to deploy multiple storage accounts but not getting good examples or documentation on how to create child resources for iterated resources in bicep
#allowed([
'CACN'
'CAEA'
'USE2'
'USCN'
])
param regionCode string
param mandatoryTags object
param sku object = {
name: 'Standard_LRS'
tier: 'Standard'
}
param identity object = {
type: 'SystemAssigned'
}
#allowed([
'None'
'Logging'
'Metrics'
'AzureServices'
])
param bypass string = 'AzureServices'
// array of storage account names
param storageAccounts array
// variables
var kind = 'StorageV2'
var varHTTPSOnly = true
var varEnableNFSv3 = false
var varAllowBlobPublicAccess = false
var tlsVersion = 'TLS1_2'
var enableADLSgen2 = false
resource storageAccountResources 'Microsoft.Storage/storageAccounts#2021-02-01' = [for storageName in storageAccounts: {
name: storageName
location: resourceGroup().location
kind: kind
sku: sku
tags: mandatoryTags
identity: identity
properties: {
accessTier: 'Hot'
supportsHttpsTrafficOnly: varHTTPSOnly
isHnsEnabled: enableADLSgen2
minimumTlsVersion: tlsVersion
isNfsV3Enabled: varEnableNFSv3
allowBlobPublicAccess: varAllowBlobPublicAccess
// network security controls
networkAcls: {
bypass: bypass
defaultAction: 'Deny'
resourceAccessRules: []
virtualNetworkRules: []
ipRules: []
}
routingPreference: {
routingChoice: 'MicrosoftRouting'
publishMicrosoftEndpoints: false
publishInternetEndpoints: false
}
encryption: {
services: {
blob: {
enabled: true
keyType: 'Account'
}
file: {
enabled: true
keyType: 'Account'
}
table: {
enabled: true
keyType: 'Account'
}
queue: {
enabled: true
keyType: 'Account'
}
}
keySource: 'Microsoft.Storage'
}
}
}]
output stgOutput array = [for (name, i) in storageAccounts: {
name: storageAccounts[i].name
}]
resource ${storageName}_default 'Microsoft.Storage/storageAccounts/blobServices#2021-04-01' = {
name: 'default'
properties: {
changeFeed: {
enabled: false
}
restorePolicy : {
enabled: false
}
containerDeleteRetentionPolicy: {
enabled: true
days: 7
}
cors: {
corsRules: []
}
deleteRetentionPolicy: {
enabled: true
days: 7
}
isVersioningEnabled: true
}
}
Looking at the documentation (Iteration for a child resource), you should be able to use an iterator :
resource blobServices 'Microsoft.Storage/storageAccounts/blobServices#2021-04-01' = [for i in range(0, length(storageAccounts)): {
name: '${storageAccountResources[i].name}/default'
properties: {
changeFeed: {
enabled: false
}
restorePolicy: {
enabled: false
}
containerDeleteRetentionPolicy: {
enabled: true
days: 7
}
cors: {
corsRules: []
}
deleteRetentionPolicy: {
enabled: true
days: 7
}
isVersioningEnabled: true
}
}]
For complex arrays, you can use this syntax as well:
resource blobServices 'Microsoft.Storage/storageAccounts/blobServices#2021-04-01' = [for (storageAccount, i) in storageAccounts: {
name: '${storageAccountResources[i].name}/default'
properties: {
changeFeed: {
enabled: storageAccount.changeFeed
}
restorePolicy: {
enabled: storageAccount.restorePolicy
}
...
}
}]