I aim to create such a bicep file that would deploy a function app with an event trigger in it, however, I'm not sure if it's possible. I've tried googling but Function App deployment via bicep tutorials tend to stop right after the deployment of an empty function app. I need to go a step further and add triggers to it.
Why do I want to deploy at least an empty event trigger via bicep?
The core of the issue is: I would like to perform two stages of CD, where the first one will be all about bicep (preparing all of the resources) and the second one about code deployment.
However, at the first CD stage, the following error occurs:
{"code":"Endpoint validation","message":"Destination endpoint not
found. Resource details: resourceId:
/subscriptions/.../functionAppNameHere/functions/eventTriggerFuncName.
Resource should pre-exist before attempting this operation.}
I assume that EventGrid EventSubscriptions requires prior presence of a resource with the trigger (seems understandable though).
Is it even possible to add an event trigger via bicep so it would move smoothly to event subscription creation? If yes: can you please give me some tips?, if no: do you have any ideas on how to circumvent this?
Here are my bicep files that may come in handy when thinking about the issue:
main.bicep
module azStorageAccount 'modules/azStorageAccount.bicep'= {
name: '${deployName}-st'
params: {
location: location
name: '${stNamePrefix}${resourceSuffix}'
}
}
module azAppInsights 'modules/azAppInsights.bicep' = {
name: '${deployName}-appi'
params: {
location: location
name: '${appiNamePrefix}${resourceSuffix}'
}
}
module azHostingPlan 'modules/azHostingPlan.bicep' = {
name: '${deployName}-asp'
params: {
location: location
name: '${aspNamePrefix}${resourceSuffix}'
}
}
module azFunctionApp 'modules/azFunctionApp.bicep' = {
name: '${deployName}-func'
params: {
storageId:azStorageAccount.outputs.storageId
storageName: azStorageAccount.outputs.storageName
azAppInsightsInstrumentationKey: azAppInsights.outputs.azAppInsightsInstrumentationKey
location: location
name: '${funcNamePrefix}${resourceSuffix}'
serverFarmId: azHostingPlan.outputs.azHostingPlaniD
}
}
module azEventGrid 'modules/azEventGrid.bicep' = {
name: '${deployName}-evg'
params: {
subscription: subscription
evgtName: '${evgtNamePrefix}${resourceSuffix}'
evgsName: '${evgsNamePrefix}${resourceSuffix}'
resourceId: '/subscriptions/${subscription}/resourceGroups/${resourceGroup}/providers/Microsoft.Web/sites/${funcNamePrefix}${resourceSuffix}/functions/${eventFunction}'
}
}
modules/azFunctionApp
// params here
resource azFunctionApp 'Microsoft.Web/sites#2021-03-01' = {
name: name
kind: kind
location: location
identity: {
type: 'SystemAssigned'
}
properties: {
httpsOnly: true
serverFarmId: serverFarmId
clientAffinityEnabled: true
reserved: true
siteConfig: {
appSettings: [
{
name: 'FUNCTIONS_EXTENSION_VERSION'
value: '~3'
}
// more settings here
]
alwaysOn: false
}
}
}
modules/azEventGrid
// params here
resource azEventGridSystemTopic 'Microsoft.EventGrid/systemTopics#2022-06-15' = {
name: evgtName
location: 'global'
tags: {
// tags
}
properties: {
source: '/subscriptions/${subscription}'
topicType: 'Microsoft.Resources.Subscriptions'
}
}
resource azEventGridEventSubscriptions 'Microsoft.EventGrid/systemTopics/eventSubscriptions#2022-06-15' = {
parent: azEventGridSystemTopic
name: evgsName
properties: {
destination: {
properties: {
resourceId: resourceId
maxEventsPerBatch: 1
preferredBatchSizeInKilobytes: 64
}
endpointType: 'AzureFunction'
}
filter: {
includedEventTypes: [
'Microsoft.Resources.ResourceWriteSuccess'
'Microsoft.Resources.ResourceDeleteSuccess'
]
enableAdvancedFilteringOnArrays: true
}
labels: []
eventDeliverySchema: 'EventGridSchema'
retryPolicy: {
maxDeliveryAttempts: 30
eventTimeToLiveInMinutes: 1440
}
}
}
How to stick app config settings to a slot via bicep?
Here is my bicep file:
var stagingSettings = [
{
name: 'AzureFunctionsJobHost__extensions__durableTask__hubName'
value: 'staging'
slotSetting: true
}
]
resource functionApp 'Microsoft.Web/sites/slots#2018-11-01' = {
name: name
kind: kind
location: location
properties: {
clientAffinityEnabled: true
enabled: true
httpsOnly: true
serverFarmId: resourceId('Microsoft.Web/serverfarms', servicePlanName)
siteConfig: {
use32BitWorkerProcess : false
appSettings: stagingSettings
}
}
identity: {
type: 'SystemAssigned'
}
}
On deploying this code, I don't see app config settings stick to a slot:
checkbox is not checked. What am I missing?
You need to create a slotConfigNames resource:
Names for connection strings, application settings, and external Azure storage account configuration
identifiers to be marked as sticky to the deployment slot and not moved during a swap operation.
This is valid for all deployment slots in an app.
Something like that should work:
param functionAppName string
resource functionApp 'Microsoft.Web/sites#2018-11-01' existing = {
name: functionAppName
}
resource functionApps 'Microsoft.Web/sites/config#2021-03-01' = {
name: 'slotConfigNames'
parent: functionApp
properties: {
// Sticky app settings
appSettingNames: [
'AzureFunctionsJobHost__extensions__durableTask__hubName'
]
}
}
I'm trying to deploy a .NET Core 3.1 Azure App Service on Linux using a Bicep template from Azure CLI. The app service and corresponding app service plan are deployed correctly but the app service stack settings are empty on the Azure portal and I have to set these manually. I tried setting the metadata property on the 'Microsoft.Web/sites' resource and also on the 'Microsoft.Web/sites/config' resource, but the result was the same.
This is my app service plan:
resource appServicePlan 'Microsoft.Web/serverfarms#2021-02-01' = {
name: 'MyAppService'
location: resourceGroup().location
properties: {
reserved: true
}
sku: {
name: 'P1v2'
}
kind: 'linux'
}
Here is my first attempt to set the stack using 'Microsoft.Web/sites' as suggested here:
https://github.com/Azure/bicep/issues/3314
resource appService 'Microsoft.Web/sites#2021-02-01' = {
name: 'MyApp'
location: resourceGroup().location
identity: {
type: 'SystemAssigned'
}
kind: 'app'
properties: {
enabled: true
serverFarmId: appServicePlan.id
siteConfig: {
linuxFxVersion: 'dotnet|3.1'
appCommandLine: 'dotnet MyApp.dll'
metadata: [
{
name: 'CURRENT_STACK'
value: 'dotnetcore'
}
]
}
}
}
Here is my second attempt to set the stack using 'Microsoft.Web/sites/config' as suggested here:
Bicep - How to config Runtime Stack to Azure App Service (Bicep version 0.4)
resource appService 'Microsoft.Web/sites#2021-02-01' = {
name: 'MyApp'
location: resourceGroup().location
identity: {
type: 'SystemAssigned'
}
kind: 'app'
properties: {
enabled: true
serverFarmId: appServicePlan.id
siteConfig: {
linuxFxVersion: 'dotnet|3.1'
appCommandLine: 'dotnet MyApp.dll'
}
}
resource webConfig 'config' = {
name: 'web'
properties: {
metadata: [
{
name: 'CURRENT_STACK'
value: 'dotnetcore'
}
]
}
}
}
The result is the same. The deployment is completed with the following warning:
Warning BCP037: The property "metadata" is not allowed on objects of
type "SiteConfig". Permissible properties include
"acrUseManagedIdentityCreds", "acrUserManagedIdentityID", "alwaysOn",
"apiDefinition", "apiManagementConfig", "autoHealEnabled",
"autoHealRules", "autoSwapSlotName", "azureStorageAccounts",
"connectionStrings", "cors", "defaultDocuments",
"detailedErrorLoggingEnabled", "documentRoot", "experiments",
"ftpsState", "functionAppScaleLimit",
"functionsRuntimeScaleMonitoringEnabled", "handlerMappings",
"healthCheckPath", "http20Enabled", "httpLoggingEnabled",
"ipSecurityRestrictions", "javaContainer", "javaContainerVersion",
"javaVersion", "keyVaultReferenceIdentity", "limits", "loadBalancing",
"localMySqlEnabled", "logsDirectorySizeLimit", "managedPipelineMode",
"managedServiceIdentityId", "minimumElasticInstanceCount",
"minTlsVersion", "netFrameworkVersion", "nodeVersion",
"numberOfWorkers", "phpVersion", "powerShellVersion",
"preWarmedInstanceCount", "publicNetworkAccess", "publishingUsername",
"push", "pythonVersion", "remoteDebuggingEnabled",
"remoteDebuggingVersion", "requestTracingEnabled",
"requestTracingExpirationTime", "scmIpSecurityRestrictions",
"scmIpSecurityRestrictionsUseMain", "scmMinTlsVersion", "scmType",
"tracingOptions", "use32BitWorkerProcess", "virtualApplications",
"vnetName", "vnetPrivatePortsCount", "vnetRouteAllEnabled",
"websiteTimeZone", "webSocketsEnabled", "windowsFxVersion",
"xManagedServiceIdentityId". If this is an inaccuracy in the
documentation, please report it to the Bicep Team.
[https://aka.ms/bicep-type-issues]
The resources are deployed, but the app service stack setting is blank and I have to set it manually to make it work.
I know that in the ARM template this is set on the CURRENT_STACK property of the Microsoft.Web/sites/config metadata (as suggested here https://cloudstep.io/2020/11/18/undocumented-arm-oddities-net-core-app-services/). However, this doesn't seem to be supported (yet) in Bicep. If anyone has found a working solution, please post it here.
Thanks.
The Metadata parameter is not available anymore in the SiteConfig. The stack setting can be mentioned LinuxFxVersion.
So, solution will be Instead of using dotnet|3.1 , You should use DOTNETCORE|3.1.The over all code will be as below:
resource appServicePlan 'Microsoft.Web/serverfarms#2021-02-01' = {
name: 'MyAppService'
location: resourceGroup().location
properties: {
reserved: true
}
sku: {
name: 'P1v2'
}
kind: 'linux'
}
resource appService 'Microsoft.Web/sites#2021-02-01' = {
name: 'anumantestapp'
location: resourceGroup().location
identity: {
type: 'SystemAssigned'
}
kind: 'app'
properties: {
enabled: true
serverFarmId: appServicePlan.id
siteConfig: {
linuxFxVersion: 'DOTNETCORE|3.1'
appCommandLine: 'dotnet MyApp.dll'
}
}
}
Ouptut:
I am working on building an azure bicep file to deploy Azure resources.
Within the same bicep file, I am creating a storage account, a couple of containers, and some management policies. Within the Microsoft documentation:
Before you configure a lifecycle management policy, you can choose to enable blob access time tracking. When access time tracking is enabled, a lifecycle management policy can include an action based on the time that the blob was last accessed with reading or write operation.
Though I am following the documentation in terms of enabling the last access time tracking policy according to the documentation, and running the management policies within the same bicep file, I am still running into this error:
"At least one resource deployment operation failed. Please list deployment operations for details. Please see https://aka.ms/DeployOperations for usage details. Last access time based tracking policy must be enabled before using its specific actions in object lifecycle management policy"
Here is my bicep file:
resource storage_account_blob 'Microsoft.Storage/storageAccounts#2019-06-01' = {
name: 'test'
location: 'East US'
sku: {
name: 'Standard_RAGRS'
}
kind: 'StorageV2'
properties: {
networkAcls: {
bypass: 'AzureServices'
virtualNetworkRules: []
ipRules: []
defaultAction: 'Allow'
}
supportsHttpsTrafficOnly: true
encryption: {
services: {
file: {
keyType: 'Account'
enabled: true
}
blob: {
keyType: 'Account'
enabled: true
}
}
keySource: 'Microsoft.Storage'
}
}
}
resource blobStorageService 'Microsoft.Storage/storageAccounts/blobServices#2019-06-01' = {
parent: storage_account_blob
name: 'default'
properties: {
lastAccessTimeTrackingPolicy: {
blobType: [
'string'
]
enable: true
name: 'AccessTimeTracking'
trackingGranularityInDays: 1
}
}
}
resource blobStorage_container_input 'Microsoft.Storage/storageAccounts/blobServices/containers#2019-06-01' = {
name: 'input'
properties: {
defaultEncryptionScope: '$account-encryption-key'
denyEncryptionScopeOverride: false
publicAccess: 'None'
}
parent: blobStorageService
}
resource management_policies 'Microsoft.Storage/storageAccounts/managementPolicies#2019-06-01' = {
name: 'default'
properties: {
policy:{
rules: [
{
definition:{
actions:{
baseBlob:{
delete:{
daysAfterLastAccessTimeGreaterThan: 60
}
tierToArchive:{
daysAfterLastAccessTimeGreaterThan: 30
}
tierToCool:{
daysAfterLastAccessTimeGreaterThan:15
}
}
}
filters:{
blobTypes:[
'blockBlob'
]
}
}
enabled: true
name: 'testRules'
type: 'Lifecycle'
}
]
}
}
parent: storage_account_blob
}
Do I have to initially maybe create the storage account with the blob service prior to creating the life cycle management policies?
I'm not sure, but have you tried to set an "depends on" on the "resource management_policies" and point it on "resource blobStorageService".
I am currently trying to deploy out a resource group using azure bicep, however, I am running into an issue using key vault for my azure app service. I would like to know if I am actually doing this the correct way. I have a main bicep file that is along the lines of:
// params removed for brevity...
targetScope = 'subscription'
resource rg 'Microsoft.Resources/resourceGroups#2021-04-01' = {
name: 'rg-${appName}-${region}'
location: 'centralus'
}
module appServicePlan 'appplan.bicep' = {
params: {
sku: appServicePlanSku
appName: appName
region: region
}
scope: rg
name: 'AppServicePlanDeploy'
}
module keyVault 'keyvault.bicep' = {
params: {
keyVaultName: keyVaultName
sqlPassword: sqlServerPassword
webSiteManagedId: webSite.outputs.webAppPrincipal
}
scope: rg
name: 'KeyVaultDeploy'
dependsOn: [
webSite
]
}
module ai 'ai.bicep' = {
scope: rg
name: 'ApplicationInsightsDeploy'
params: {
name: appName
region: region
keyVaultName: keyVault.outputs.keyVaultName
}
dependsOn: [
keyVault
]
}
resource kv 'Microsoft.KeyVault/vaults#2019-09-01' existing = {
name: keyVaultName
scope: rg
}
module sql 'sqlserver.bicep' = {
scope: rg
name: 'SQLServerDeploy'
params: {
appName: appName
region: region
sqlPassword: kv.getSecret('sqlPassword')
sqlCapacitity: sqlCapacitity
sqlSku: sqlSku
sqlTier: sqlTier
}
dependsOn: [
keyVault
]
}
module webSite 'site.bicep' = {
params: {
appName: appName
region: region
keyVaultName: keyVaultName
serverFarmId: appServicePlan.outputs.appServicePlanId
}
scope: rg
name: 'AppServiceDeploy'
dependsOn: [
appServicePlan
]
}
My question comes with the implementation of the site.bicep, I started off by passing the secret uri from exported variables and creating the web app last as app insights, sql, etc... all need to be setup and in keyvault before we use their exported secret uri to construct a config. I had something along the lines of:
site.bicep (before):
properties: {
serverFarmId: serverFarmId
keyVaultReferenceIdentity: userAssignedId
siteConfig: {
appSettings: [
{
name: 'APPLICATIONINSIGHTS_CONNECTION_STRING'
value: '#Microsoft.KeyVault(SecretUri=${appInsightsConnectionString})'
}
{
name: 'APPINSIGHTS_INSTRUMENTATIONKEY'
value: '#Microsoft.KeyVault(SecretUri=${appInsightsKey})'
}
]
netFrameworkVersion: 'v5.0'
}
}
The only problem with this implementation is that the key vault MUST be constructed before the website because sql, ai, and the other services will store their values inside of the key vault for the web app to consume by their respective uris. The issue with this is that the KeyVault rightfully so has no idea which azure service to let access it's keys.
My question is the solution of constructing the web app before the key vault the only way to beat this problem? I am using managed identities on the web app and would like to continue doing so if possible. My final solution ended up somewhat like this:
site.bicep (final)
// params removed for brevity...
resource webApplication 'Microsoft.Web/sites#2020-12-01' = {
name: 'app-${appName}-${region}'
location: resourceGroup().location
tags: {
'hidden-related:${resourceGroup().id}/providers/Microsoft.Web/serverfarms/appServicePlan': 'Resource'
}
identity: {
type: 'SystemAssigned'
}
properties: {
serverFarmId: serverFarmId
siteConfig: {
appSettings: [
{
name: 'APPLICATIONINSIGHTS_CONNECTION_STRING'
value: '#Microsoft.KeyVault(SecretUri=${keyVaultName}.vault.azure.net/secrets/aiConnectionString)'
}
{
name: 'APPINSIGHTS_INSTRUMENTATIONKEY'
value: '#Microsoft.KeyVault(SecretUri=${keyVaultName}.vault.azure.net/secrets/aiInstrumentationKey)'
}
{
name: 'AngularConfig:ApplicationInsightsKey'
value: '#Microsoft.KeyVault(SecretUri=${keyVaultName}.vault.azure.net/secrets/aiInstrumentationKey)'
}
]
netFrameworkVersion: 'v5.0'
}
}
}
output webAppPrincipal string = webApplication.identity.principalId
And the KeyVault which will take a dependsOn webSite
keyVault.bicep(final):
resource keyVault 'Microsoft.KeyVault/vaults#2019-09-01' = {
name: keyVaultName
location: resourceGroup().location
properties: {
enabledForDeployment: true
enabledForTemplateDeployment: true
enabledForDiskEncryption: true
enableRbacAuthorization: true
tenantId: subscription().tenantId
sku: {
name: 'standard'
family: 'A'
}
accessPolicies: [
{
tenantId: subscription().tenantId
objectId: webSiteManagedId
permissions: {
keys: [
'get'
]
secrets: [
'list'
'get'
]
}
}
]
}
}
Just treat your accessPolicies as separate resource and add them when both Key Vault and App Service are created. Same applies for Config section and Connection Strings. Check documentation here.
In ARM templates you can achieve same effect using nested templates. In Bicep it is kind the same, but you declare them as separate resource that usually contains parent name (e.g. name: '${kv.name}/add', name: '${webSite.name}/connectionstrings')
Sample
Step 1: Create an App Service without config section
resource webSite 'Microsoft.Web/sites#2020-12-01' = {
name: webSiteName
location: location
properties: {
serverFarmId: hostingPlan.id
siteConfig:{
netFrameworkVersion: 'v5.0'
}
}
identity: {
type:'SystemAssigned'
}
}
Step 2: Create Key Vault without access policies
resource kv 'Microsoft.KeyVault/vaults#2019-09-01' = {
name: keyVaultName
location: location
properties:{
sku:{
family: 'A'
name: 'standard'
}
tenantId: tenantId
enabledForTemplateDeployment: true
accessPolicies:[
]
}
}
Step 3: Create new access policy and reference Web Apps Managed Identity
resource keyVaultAccessPolicy 'Microsoft.KeyVault/vaults/accessPolicies#2021-06-01-preview' = {
name: '${kv.name}/add'
properties: {
accessPolicies: [
{
tenantId: tenantId
objectId: webSite.identity.principalId
permissions: {
keys: [
'get'
]
secrets: [
'list'
'get'
]
}
}
]
}
}
Step 4: Update Webb app config section
resource webSiteConnectionStrings 'Microsoft.Web/sites/config#2020-06-01' = {
name: '${webSite.name}/connectionstrings'
properties: {
DefaultConnection: {
value: '#Microsoft.KeyVault(SecretUri=${keyVaultName}.vault.azure.net/secrets/aiConnectionString)'
type: 'SQLAzure'
}
}
}
One solution could be to use User Assigend Identity instead of System Assigned. Then you would deploy the following:
Deploy a user assigend identity
Key Vault and assign permissions for user assigned identity
Deploy web app with user assigned identity and read / write secrets
User assigned is independent of the resources and so you avoid your chicken and egg problem.
More:
https://learn.microsoft.com/en-us/azure/templates/microsoft.managedidentity/userassignedidentities?tabs=bicep
https://learn.microsoft.com/en-us/azure/active-directory/managed-identities-azure-resources/overview
You should split up three parts of your deployment into separate resources:
Deploy the Key Vault - without any access policies!
Deploy the App Service - with the SystemAssigned Identity, but without the app settings
Deploy the Key Vault Access Policy for the MSI
Deploy the App Settings