Can I deploy an event trigger to Function App via Bicep? - azure

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
}
}
}

Related

Deploy Logic App (Std) Workflow Disabled with Bicep/YAML

I have tried numerous methods and the workflow is still deployed enabled.
My latest attempt was to use the resource in my bicep:
resource workflow 'Microsoft.Web/sites/workflows#2015-08-01' = {
name: workflowName
dependsOn: [ logicContainer ]
location: location
kind: 'Stateful'
properties: {
flowstate: 2
}
}
However, the workflow never appears in the Logic App function.
I cannot even deploy the container in the 'Stopped' state, since for Microsoft.Web/sites#2022-03-01, state is readonly.
In my yaml I send a the workflows in a pipe separated string e.g. "wf-one|wf-two|wf-three|wf-four"
Then, in my bicep I populate an array of Workflow states app settings:
var wfAppSettingStatuses = [ for wf in split(workflows,'|'): {
name: 'Workflows.${wf}.FlowState'
value: 'Disabled'
}]
To add to the Logic App container configuration settings use the union function:
resource logicContainer 'Microsoft.Web/sites#2022-03-01' = {
name: appName
location: location
kind: 'functionapp,workflowapp'
identity: {
type: 'SystemAssigned'
}
properties: {
httpsOnly: true
siteConfig: {
appSettings: union(wfAppSettingStatuses, [
{
name: 'APPINSIGHTS_INSTRUMENTATIONKEY'
value: applicationInsights.properties.InstrumentationKey
}
...
])
use32BitWorkerProcess: true
}
serverFarmId: planId
clientAffinityEnabled: false
vnetRouteAllEnabled: true
storageAccountRequired: false
keyVaultReferenceIdentity: 'SystemAssigned'
}
}
Each workflow in the list is now disabled, therefore if you require one to be enabled on deployment, remove it from the pipe delimited string.

Azure Bicep - Timing of Dependent Module

I'd like a module that could add a fixed set of ip restriction rules to an existing app service (function app in this case).
I have added a call to a "ipSecurityRestrictions" module from my main.bicep as follows:
module ipRestrictions 'common.appSvc.ipSecurityRestrictions.bicep' = {
scope: resourceGroup(utrnRg)
name: 'ipRestrictionsDeploy'
params: {
appSvcName: functionAppName
existingIpSecurityRestrictions: reference(resourceId('Microsoft.Web/sites/config', functionAppName, 'web'), '2021-02-01').ipSecurityRestrictions
}
dependsOn: [
functionAppDeploy
]
}
The code for the "ipSecurityRetrictions" module is:
param appSvcName string
param existingIpSecurityRestrictions array = []
resource appSvc 'Microsoft.Web/sites#2021-02-01' existing = {
name: appSvcName
}
var proxyIpAddresses = ['xxx.xxx.xxx.250/32','xxx.xxx.xxx.245/32','xxx.xxx.xxx.251/32']
var proxyIpRestrictions = [for (ip,i) in proxyIpAddresses: {
ipAddress: ip
action: 'Allow'
tag: 'Default'
priority: 900 + i
name: 'ProxyIp_${i}'
description: 'Allow request from proxy ${i}'
}]
resource sitesConfig 'Microsoft.Web/sites/config#2021-02-01' = {
name: 'web'
parent: appSvc
properties: {
ipSecurityRestrictions: concat(existingIpSecurityRestrictions, proxyIpRestrictions)
}
}
The call to the function app module is as follows:
module functionAppDeploy 'utrngen.functionApp.bicep' = {
name: 'functionAppDeploy'
scope: resourceGroup(utrnRg)
params: {
pcPublicIp: pcPublicIp
functionAppName: functionAppName
}
dependsOn: [
appPlanDeploy
storageAccountDeploy
]
}
The function app moddule has a sites/config resource to create ipSecurityRestrictions as follows:
resource sitesConfig 'Microsoft.Web/sites/config#2021-02-01' = {
name: 'web'
parent: functionApp
properties: {
ipSecurityRestrictions: [
{
ipAddress: '${pcPublicIp}/32'
action: 'Allow'
tag: 'Default'
priority: 101
name: 'laptop ip'
description: 'Allow requests from test laptop'
}
]
}
The problem is, when main.bicep is run, it only adds the new set of rules to those that already existed; any rules specified in the function app module (in this case the one for pcPublicIp) are not added.
I guess this is because at the time the call to the ipRestrictions module is made from main.bicep, the ipSecurityRestrictions from the function app module have not yet been created; and so the following function call only returns what's present before the main.bicep is run:
existingIpSecurityRestrictions: reference(resourceId('Microsoft.Web/sites/config', functionAppName, 'web'), '2021-02-01').ipSecurityRestrictions
So I think bicep is working as expected but curious if there's a solution this this problem? I could simply pass in the dedicated ip restriction rules for the function app as a parameter to the shared ipSecurityRestrictions module but that has a bad smell, since it breaks single responsibility for the ipSecurityRestrictions module. It would no longer be responsible for just adding the ip restrictions that are common to all our app services.
You could move the call to the common module inside the functionapp module:
// utrngen.functionApp.bicep
...
module ipRestrictions 'common.appSvc.ipSecurityRestrictions.bicep' = {
name: 'ipRestrictionsDeploy-${functionAppName}'
params: {
appSvcName: functionApp.name
existingIpSecurityRestrictions: reference(resourceId('Microsoft.Web/sites/config', functionApp.name, 'web'), '2021-02-01').ipSecurityRestrictions
}
}
Or you could return the ipSecurityRestrictions from your function app module and use it in your main:
// utrngen.functionApp.bicep
...
output ipSecurityRestrictions array = sitesConfig.properties.ipSecurityRestrictions
// main.bicep
...
module ipRestrictions 'common.appSvc.ipSecurityRestrictions.bicep' = {
name: 'ipRestrictionsDeploy-${functionAppName}'
params: {
appSvcName: functionAppName
existingIpSecurityRestrictions: functionAppDeploy.outputs.ipSecurityRestrictions
}
}
Or you could wrap the call to the ip restriction module inside another module
// common.appSvc.ipSecurityRestrictions-wrapper.bicep
param appSvcName string
module ipRestrictions 'common.appSvc.ipSecurityRestrictions.bicep' = {
name: 'ipRestrictionsDeploy-${appSvcName}'
params: {
appSvcName: appSvcName
existingIpSecurityRestrictions: reference(resourceId('Microsoft.Web/sites/config', appSvcName, 'web'), '2021-02-01').ipSecurityRestrictions
}
}
// main.bicep
...
module ipRestrictions 'common.appSvc.ipSecurityRestrictions-wrapper.bicep' = {
name: 'ipRestrictionsDeploy-${functionAppName}'
params: {
appSvcName: functionAppName
}
dependsOn: [ functionAppDeploy ]
}

How to stick app config settings to a slot via bicep

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'
]
}
}

How can I combine dynamic settings and static settings in a Function App using Bicep?

I am trying to defines settings (static and dynamic) for a Function App using Bicep.
The following is the module that generates the Function App itself. This module contains a loop that creates a collection of dynamic settings (which does work):
param serverFarmId string
param availableHubs array
resource functionAppProdSlotResource 'Microsoft.Web/sites#2021-03-01' = {
name:'function-app-name'
location: 'Canada Central'
kind: 'functionapp,linux'
identity: {
type: 'SystemAssigned'
}
properties: {
serverFarmId: serverFarmId
httpsOnly: true
siteConfig: {
linuxFxVersion: 'dotnet|3.1'
appSettings: [for (availableHubId, hubIndex) in availableHubs: {
'name': 'AvailableHubsConfiguration__Hub__${hubIndex}'
'value': 'Endpoint=https://hub-${availableHubId}.service.signalr.net;AccessKey=${listKeys(resourceId('Microsoft.SignalRService/SignalR', 'hub-${availableHubId}'), providers('Microsoft.SignalRService', 'SignalR').apiVersions[0]).primaryKey};Version=1.0;'
}]
}
}
}
However I also have this other module that defines static settings for that same function:
param functionIdentity object = {
prodSlotName: ''
stagingSlotName: ''
}
#secure()
param systemStorageAccountConnectionString string
param applicationInsightsInstrumentationKey string
param stsDiscoveryEndpoint string
param accountsManagerEndpoint string
param azureActiveDirectory object
param updateManagerClientIdKeyVaultUrl string
param updateManagerClientSecretKeyVaultUrl string
var functionExtensionVersion = '~4'
var functionWorkerRuntime = 'dotnet-isolated'
resource prodSlotAppSettingsResource 'Microsoft.Web/sites/config#2021-03-01' = {
name: '${functionIdentity.prodSlotName}/appsettings'
properties: {
AzureWebJobsStorage: systemStorageAccountConnectionString
FUNCTIONS_EXTENSION_VERSION: functionExtensionVersion
FUNCTIONS_WORKER_RUNTIME: functionWorkerRuntime
APPINSIGHTS_INSTRUMENTATIONKEY: applicationInsightsInstrumentationKey
StsConfiguration__DiscoveryEndpoints__0: stsDiscoveryEndpoint
AccountsManagerConfiguration__Endpoint: accountsManagerEndpoint
AzureActiveDirectoryConfiguration__ClientId: '#Microsoft.KeyVault(SecretUri=${updateManagerClientIdKeyVaultUrl})'
AzureActiveDirectoryConfiguration__ClientSecret: '#Microsoft.KeyVault(SecretUri=${updateManagerClientSecretKeyVaultUrl})'
AzureActiveDirectoryConfiguration__Scope: azureActiveDirectory.scope
AzureActiveDirectoryConfiguration__TokenEndpoint: azureActiveDirectory.tokenEndpoint
AzureActiveDirectoryConfiguration__TenantId: subscription().tenantId
AzureActiveDirectoryConfiguration__SubscriptionId: subscription().subscriptionId
}
}
Problem
The problem is that the 2nd module overrides the dynamic settings set by the 1st module. Because of the loop in the 1st module, I can't find a way to either prevent the override by the 2nd module or somehow combine the two.
Question
How can I combine dynamic settings and static settings in a Function App using Bicep?
I struggled for a few weeks to find an actual solution. What I have found rely on the concat function.
I dropped the 2nd module (which used to define a resource for the Function's settings) and only keep the 1st one (which had the Function resource itself along with the dynamic settings).
I now call the 1st module with an array that I built:
module functionAppModule 'modules/function-app.bicep' = {
name: 'Function_App'
dependsOn: [
serverFarmModule
]
params: {
[...]
availableHubs: [for availableHub in availableHubs: 'Endpoint=https://hub-${environment}-${availableHub}.service.signalr.net;AccessKey=${listKeys(resourceId('Microsoft.SignalRService/SignalR', 'hub-${environment}-${availableHub}'), providers('Microsoft.SignalRService', 'SignalR').apiVersions[0]).primaryKey};Version=1.0;']
}
}
In the module, I build 2 distinct arrays: one with the static settings and another one with the dynamic settings:
var staticSettings = [
{
name: 'FUNCTIONS_EXTENSION_VERSION'
value: '~4'
}
{
name: 'FUNCTIONS_WORKER_RUNTIME'
value: 'dotnet-isolated'
}
{
name: 'AzureWebJobsStorage'
value: systemStorageAccountConnectionString
}
{
name: 'APPINSIGHTS_INSTRUMENTATIONKEY'
value: applicationInsightsInstrumentationKey
}
[...]
]
var dynamicSettings = [for (availableHub, hubIndex) in availableHubs: {
name: 'AvailableHubsConfiguration__Hub__${hubIndex}'
value: availableHub
}]
Then, using the concat function, I merge the arrays together:
var functionSettings = concat(staticSettings, dynamicSettings)
Finally, I can assign the merged functionSettings array to the Function App by looping over it and specifying the name & value:
resource functionAppProdSlotResource 'Microsoft.Web/sites#2021-03-01' = {
name: 'function-app-name'
location: 'Canada Central'
kind: 'functionapp,linux'
identity: {
type: 'SystemAssigned'
}
properties: {
serverFarmId: serverFarmId
httpsOnly: true
siteConfig: {
linuxFxVersion: 'dotnet|3.1'
appSettings: [for setting in functionSettings: {
name: setting.name
value: setting.value
}]
}
}
}

Setting Azure App Service server stack on a Bicep template

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:

Resources