How to return output of module - azure

I am creating two Azure App Services and 2 App Service Plans using For Loop but I need to pass the HostingPlanID from the AppService plan to each of the App Services, but I'm unable to access the output of the module.
I'm getting the following error
Cannot access properties of type "module[]". An "object" type is required.
within my App Service Plan Module, I am creating an output output hostingPlanId string = hostingPlan.id
But i can't reference this from my template (main.bicep)
hostingPlan: AppServicePlan.hostingPlan.id
Module:
param appPlanSkuCode string
param appPlanSkuTier string
param appPlanOS string
param appPlanSize string
param appPlanFamily string
param appPlanCapacity int
param hostingPlanName string
resource hostingPlan 'Microsoft.Web/serverfarms#2020-10-01' = {
name: hostingPlanName
location: resourceGroup().location
kind: appPlanOS
sku: {
name: appPlanSkuCode
tier: appPlanSkuTier
size: appPlanSize
family: appPlanFamily
capacity: appPlanCapacity
}
properties: {
perSiteScaling: false
maximumElasticWorkerCount: 1
isSpot: false
reserved: true
isXenon: false
hyperV: false
targetWorkerCount: 0
targetWorkerSizeId: 0
}
}
output hostingPlanId string = hostingPlan.id
Template:
module AppService '../../Modules/Azure.App.Service.template.bicep' = [for i in range(0, length(webAppSettings.webApps)): {
name: webAppSettings.webApps[i].appServiceType == 'functionApp' ? toLower('fnc-${webAppSettings.webApps[i].name}-${resourceGroupNameSuffix}') : toLower('web-${webAppSettings.webApps[i].name}-${resourceGroupNameSuffix}')
dependsOn: [
AppServicePlan
]
params: {
hostingPlan: AppServicePlan.hostingPlan.id
webAppSettings:webAppSettings
vnetSubnetID:webAppSettings.webApps[i].appPurpose == 'backend' ? '${backendvnetSubnetID}' : '${frontendvnetSubnetID}'
appServiceName:webAppSettings.webApps[i].appServiceType == 'functionApp' ? toLower('fnc-${webAppSettings.webApps[i].name}-${resourceGroupNameSuffix}') : toLower('web-${webAppSettings.webApps[i].name}-${resourceGroupNameSuffix}')
appServiceType: webAppSettings.webApps[i].appServiceType
LinuxFXVersion: webAppSettings.webApps[i].LinuxFX
}
}]
Any ideas where I am going wrong?
Thanks

If you have a hostingPlan.bicep file, you can reference it from the main.bicep file like that:
module hostingPlan 'hostingPlan.bicep' = {
name: 'hostingPlan'
params: {
...
}
}
You can then access the outputs of the module:
hostingPlan.outputs.hostingPlanId

Related

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

Azure Bicep and create runbooks

I am struggling with the Azure documentation and I am not finding any relevant examples that can help me defining an Azure runbook based on Bicep.
The official documentation doesn't provide much information about the possible parameters
https://learn.microsoft.com/en-us/azure/templates/microsoft.automation/automationaccounts/runbooks?pivots=deployment-language-bicep
Here is what I did
param runbookName string
param location string = resourceGroup().location
param automationAccount object
#allowed([
'Graph'
'GraphPowerShell'
'GraphPowerShellWorkflow'
'PowerShell'
'PowerShellWorkflow'
'Python2'
'Python3'
'Script'
])
param runbookType string = 'PowerShell'
param logProgress bool = true
param logVerbose bool = true
param logActivityTrace int = 0
resource runbookABC 'Microsoft.Automation/automationAccounts/runbooks#2019-06-01' = {
name: runbookName
location: location
parent: automationAccount
properties: {
description: 'Execute runbook ABC'
draft: {
draftContentLink: {
contentHash: {
algorithm: 'string' <<== 1) which values are allowed here?
value: loadTextContent('./scripts/abc.ps1') <<== 2) is this correct?
}
uri: 'string' <<== 3) what should I put here
version: 'v1.0'
}
inEdit: false
outputTypes: [
'varA' <<== 4) how the mapping is done here?
]
parameters: {}
}
logActivityTrace: logActivityTrace
logProgress: logProgress
logVerbose: logVerbose
runbookType: runbookType
}
}
As shown in the code above, I am unsure about the inputs:
contentHash > algorithm
contentHash > value
draftContentLink > uri
outputTypes
Any advices?
Thanks!

Azure SQL failover group using Bicep

I am trying to implement Azure SQL failover group using Bicep templates. The problem I have is, I am unable to figure out how to reference array of databases passed as parameter in failover group's database property. When assigning array values to database property it gives me an error of expecting string values whereas assigned was array, which of course isn't correct.
resource symbolicname 'Microsoft.Sql/servers/failoverGroups#2021-11-01-preview' = {
name: 'string'
tags: {
tagName1: 'tagValue1'
tagName2: 'tagValue2'
}
parent: resourceSymbolicName
properties: {
databases: [
'string'
]
partnerServers: [
{
id: 'string'
}
]
readWriteEndpoint: {
failoverPolicy: 'string'
failoverWithDataLossGracePeriodMinutes: int
}
}
}
Does anyone have any working example of Azure SQL failover group using Bicep templates or point me in the right direction on the solution?
Edit 1 - main.bicep :
This is the code that I am trying after adding Thomas's changes:
param databases array
param primarySqlServerName string
param drSqlServerName string
param failovergroupName string
module primarySql '***************' = {
Deploy primary SQL Server and database
}
module drSql '****************' = {
Deploy secondary SQL Server
}
resource sqlServerFailoverGroup 'Microsoft.Sql/servers/failoverGroups#2020-11-01-preview' = {
name: '${primarySqlServerName}/${failovergroupName}'
properties: {
databases: [for database in databases: resourceId('Microsoft.Sql/servers/databases', primarySqlServerName, database)]
readWriteEndpoint: {
failoverPolicy: 'Automatic'
failoverWithDataLossGracePeriodMinutes: 60
}
readOnlyEndpoint: {
failoverPolicy: 'Enabled'
}
partnerServers: [
{
id: resourceId('Microsoft.Sql/servers', drSqlServerName)
}
]
}
dependsOn: [
primarySql
drSql
]
}
Getting below error while deploying above:
Unable to process template language expressions for resource at line
'1' and column '1289'. 'Unable to evaluate template language function
'resourceId': all function arguments must be string literals. Please
see aka.ms/arm-template-expressions/#resourceid for usage details.
The databases property is an of string. The partnerServers property is an array of object.
Both expect the resource id of the databases and partner servers.
Here is a simple sample of deploying 2 sql servers and databases with a failover group
sql.bicep:
param location string = resourceGroup().location
param sqlServerName string
param sqlServerPrincipalType string
param sqlServerAadAdminName string
param sqlServerAadAdminId string
param databaseNames array
// Create the Server
resource sqlServer 'Microsoft.Sql/servers#2020-11-01-preview' = {
name: sqlServerName
location: location
tags: {}
properties: {
administrators: {
administratorType: 'ActiveDirectory'
principalType: sqlServerPrincipalType
login: sqlServerAadAdminName
sid: sqlServerAadAdminId
azureADOnlyAuthentication: true
}
}
}
// We create the database only in the primary region
resource database 'Microsoft.Sql/servers/databases#2020-08-01-preview' = [for databaseName in databaseNames: if (!empty(databaseNames)) {
name: '${sqlServer.name}/${empty(databaseNames) ? 'placeholder' : databaseName}'
location: location
sku: {
name: 'S0'
tier: 'Standard'
}
properties: {
sourceDatabaseId: sqlServer.id
}
}]
and the main.bicep file:
param failoverGroupName string
param primarySqlServerName string
param primaryLocation string
param secondarySqlServerName string
param secondaryLocation string
param sqlServerPrincipalType string
param sqlServerAadAdminName string
param sqlServerAadAdminId string
param databaseNames array
module primarySql 'sql.bicep' = {
name: 'primarySql'
params: {
location: primaryLocation
sqlServerName: primarySqlServerName
sqlServerPrincipalType: sqlServerPrincipalType
sqlServerAadAdminName: sqlServerAadAdminName
sqlServerAadAdminId: sqlServerAadAdminId
databaseNames: databaseNames
}
}
module secondarySql 'sql.bicep' = {
name: 'secondarySql'
params: {
location: secondaryLocation
sqlServerName: secondarySqlServerName
sqlServerPrincipalType: sqlServerPrincipalType
sqlServerAadAdminName: sqlServerAadAdminName
sqlServerAadAdminId: sqlServerAadAdminId
databaseNames: []
}
}
resource sqlServerFailoverGroup 'Microsoft.Sql/servers/failoverGroups#2020-11-01-preview' = {
name: '${primarySqlServerName}/${failoverGroupName}'
dependsOn: [ primarySql, secondarySql ]
properties: {
databases: [for dataBaseName in databaseNames: resourceId('Microsoft.Sql/servers/databases', primarySqlServerName, dataBaseName)]
readWriteEndpoint: {
failoverPolicy: 'Automatic'
failoverWithDataLossGracePeriodMinutes: 60
}
readOnlyEndpoint: {
failoverPolicy: 'Enabled'
}
partnerServers: [
{
id: resourceId(resourceGroup().name, 'Microsoft.Sql/servers', secondarySqlServerName)
}
]
}
}

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

Is there a way to deploy Azure resource modules not in the main file?

Normally when creating Azure resources through Bicep modules I would have 2 files. One file designated to hold the parameterized resource and another file, the main file, which will consume that module.
As an example for creating an action group my resource file looks like:
action-group.bicep
param actionGroupName string
param groupShortName string
param emailReceivers array = []
// Alerting Action Group
resource action_group 'microsoft.insights/actionGroups#2019-06-01' = {
name: actionGroupName
location: 'global'
tags: {}
properties: {
groupShortName: groupShortName
enabled: true
emailReceivers: emailReceivers
}
}
This resource is then consumed as a module in the main file, main.bicep:
// Alerting Action Group
module actionGroup '../modules/alerts/alert-group.bicep' = {
name: 'action-group-dply'
params: {
actionGroupName: actionGroupName
groupShortName: actionGroupShortName
emailReceivers: [
{
name: '<Name of email receivers>'
emailAddress: alertEmailList
}
]
}
}
My pipeline references main.bicep and will deploy the listed resources in the file. My question is, is there a way to add a third file in the mix? One file to still hold the parameterized resource, one file to hold the associated resource modules, and the main.bicep file. The idea is to create various alerts throughout my existing resources but I don't want to add a ton of modules to main.bicep as it will quickly increase the complexity and amount of code in this file.
Is there a way that I can have this file of modules, and reference the entire file in main.bicep to still deployment from the original pipeline?
As an example:
alerts.bicep
param metricAlertsName string
param description string
param severity int
param enabled bool = true
param scopes array = []
param evaluationFrequency string
param windowSize string
param targetResourceRegion string = resourceGroup().location
param allOf array = []
param actionGroupName string
var actionGroupId = resourceId(resourceGroup().name, 'microsoft.insights/actionGroups', actionGroupName)
resource dealsMetricAlerts 'Microsoft.Insights/metricAlerts#2018-03-01' = {
name: metricAlertsName
location: 'global'
tags: {}
properties: {
description: description
severity: severity
enabled: enabled
scopes: scopes
evaluationFrequency: evaluationFrequency
windowSize: windowSize
targetResourceRegion: targetResourceRegion
criteria: {
'odata.type': 'Microsoft.Azure.Monitor.SingleResourceMultipleMetricCriteria'
allOf: allOf
}
actions: [
{
actionGroupId: actionGroupId
}
]
}
alert-modules.bicep
// Function/Web Apps 403 Error
module appServicePlan403Errors '../modules/alerts/alerts.bicep' = {
// Alert Logic
}
// Function/Web Apps 500 Error
module appServicePlan500Errors '../modules/alerts/alerts.bicep' = {
// Alert Logic
}
main.bicep
// Some reference to alert-modules.bicep so when the pipeline runs and looks for main.bicep, it will still deploy all the resources
You can call the module multiple times (in main.bicep or a module) using looping:
https://learn.microsoft.com/en-us/azure/azure-resource-manager/bicep/loops
e.g.
param alertsCollection array = [
{
actionGroupName: 'group1'
groupShortName: 'g1'
emailReceivers: []
}
{
actionGroupName: 'group2'
groupShortName: 'g2'
emailReceivers: [
'foo#bar.com'
'bar#baz.com'
]
}
]
module alerts '../modules/alerts/alert-group.bicep' = [for alert in alertsCollection): {
name: '${alert.actionGroupName}'
params: {
actionGroupName: alert.actionGroupName
groupShortName: alert.groupShortName
emailReceivers: alert.emailReceivers
}
}]
You could simplify the params passing via:
module alerts '../modules/alerts/alert-group.bicep' = [for alert in alertsCollection): {
name: '${alert.actionGroupName}'
params: alert
}]
But you need to be really strict on the schema of the alertsCollection parameter...
That help?

Resources