How to add condition in ARM Bicep template? - azure

Let’s say, I want to deploy a separate resource only if param isProduction bool
is true.
Is that possible with Bicep?
I could not find this in the documentation.

There is a syntax if(isProduction) that can be used after =, for example:
param isProduction bool
resource prodWebApp 'Microsoft.Web/sites#2020-12-01' = if (isProduction) {
name: 'MyWebApp'
...<other props>...
}

In addition to the answer already given, not only can the whole resource be deployed based on a condition, but also individual properties of a resource can be changed for a specific environment using conditional ternary operator. The following is an example for Azure Front Door, where only in the production environment the Premium Tier is used:
resource profile 'Microsoft.Cdn/profiles#2020-09-01' = {
name: 'azureFrontDoor'
location: 'global'
sku: {
name: isProduction ? 'Premium_AzureFrontDoor' : 'Standard_AzureFrontDoor'
}
tags: tags
}

Related

Azure bicep dependsOn for existing resource

From my "main" bicep module, I would like to reference an existing function that is created by a module called from the same "main" bicep. So used the following code:
resource functionApp 'Microsoft.Web/sites#2021-02-01' existing = {
name: functionAppName
scope: resourceGroup(subscriptionId, 'rg-365response-${env}-001')
}
I am then able to use properties from the "functionApp" resource variable to obtain the function key and store as a key vault secret as follows:
resource funcSecret 'Microsoft.KeyVault/vaults/secrets#2021-04-01-preview' = {
name: '${kvName}/funcAppKey'
properties: {
value: listKeys('${functionApp.id}/host/default', functionApp.apiVersion).functionKeys.default
}
}
However, when I run a resource group deployment and see the following error:
The Resource 'Microsoft.Web/sites/func-365response-int-001' under
resource group 'rg-365response-int-001' was not found
This is some kind of timing issue, I guess it's checking for the function app before the call to the module that creates it has had chance to complete.
If I run the "main" bicep module a second time, everything works okay.
It seems it's not possible to use the "dependsOn" syntax for a resource that is "existing".
Is there an alternative?
DependOns can only be used for resources defined in the same bicep file (ARM template).
When you use the existing keyword, it will compiled to a resourceId() or reference() by Bicep
You could create a module to create secret:
// key-vault-secret.bicep
param kvName string
param secretName string
#secure()
param secretValue string
resource kvSecret 'Microsoft.KeyVault/vaults/secrets#2021-04-01-preview' = {
name: '${kvName}/${secretName}'
properties: {
value: secretValue
}
}
Then from where you are creating your function, you could invoke it like that:
resource functionApp 'Microsoft.Web/sites#2021-03-01' = {
name: functionAppName
location: location
kind: 'functionapp'
...
}
// Add secret to KV
module functionKey 'key-vault-secret.bicep' = {
name: 'function-default-host-key'
scope: resourceGroup()
params:{
kvName: kvName
secretName: 'funcAppKey'
secretValue: listKeys('${functionApp.id}/host/default', functionApp.apiVersion).functionKeys.default
}
}
I think you are correct in that the listKeys() is called too early, you can't fix it with dependsOn unfortunately. There is a bit more explanation here: https://bmoore-msft.blog/2020/07/26/resource-not-found-dependson-is-not-working/
The only fix for this is to put the listKeys and the function into different modules and make sure you have dependsOs if the second module doesn't consume an input from the first.
The part that's not adding up for me is that you have an existing keyword on the resource in the sample above but you say you're creating it. The symptoms you describe also suggest you're creating it in the same deployment. If you are, they you don't need the `existing' keyword.
If all else fails - post all the code.

How to get the host url of a linux app service in azure bicep deployment templates

I've created an app service using a bicep resource shown below
name: '${appName}'
location: location
kind: 'linux,container,fnapp'
properties: {
serverFarmId: servicePlan.id
siteConfig: {
linuxFxVersion: 'DOCKER|${dockerLocation}'
healthCheckPath: '/api/healthcheck'
alwaysOn: true
appSettings: [
...
]
}
}
}
Which works as expected, however I'd like to get the url for that app service to use as a backend url for my apim service.
I'm currently using var fnAppUrl = 'https://${fnApp.name}.azurewebsites.net/api'. Is there any way I can get the default url from the direct output of the function app resource i.e. var fnAppUrl = fnApp.url or something similar?
TIA
There is no specific property for this base path as it is set by configuring the value of extensions.http.routePrefix in host.json.
(Docs: https://learn.microsoft.com/en-us/azure/azure-functions/functions-bindings-http-webhook?tabs=in-process%2Cfunctionsv2&pivots=programming-language-csharp#hostjson-settings)
If the /api prefix is not required, then it would suffice to use the following output in your Bicep file:
resource myFunctionApp 'Microsoft.Web/sites#2021-03-01' = {
// ...
}
output functionBaseUrl string = 'https://${myFunctionApp.properties.defaultHostName}'
Then in your host.json ensure the routePrefix is set to an empty string:
{
"extensions": {
"http": {
"routePrefix": ""
}
}
}
If you do want to use a route prefix, you need to combine it with the default host name:
resource myFunctionApp 'Microsoft.Web/sites#2021-03-01' = {
// ...
}
output functionBaseUrl string = 'https://${myFunctionApp.properties.defaultHostName}/api'
As far as I know, there is no direct url you can get from the function. But you can use something like:
output functionBaseUrl string = 'https://${function.properties.defaultHostName}/api'
This will result in the default hostname including the "azurewebsites.net" part.
Check the full example in the template.bicep here:
https://github.com/DSpirit/azure-functions-bicep-outputs-host

Azure Bicep - Role assignment - Principal does not exist in the directory

I've created a Bicep template. In it I create a user-assigned identity and reference it in other resources like this
var identityName = 'mid-dd-test'
var roleName = 'TestRole'
var roleDescription = 'Some test'
var roleScopes = [
resourceGroup().id
]
var resolvedActions = [
'Microsoft.Resources/subscriptions/resourcegroups/*'
'Microsoft.Compute/sshPublicKeys/*'
]
var permittedDataActions = []
resource userId 'Microsoft.ManagedIdentity/userAssignedIdentities#2018-11-30' = {
name: identityName
location: resourceGroup().location
}
resource roleDef 'Microsoft.Authorization/roleDefinitions#2018-01-01-preview' = {
name: guid(subscription().id, 'bicep', 'dsadsd')
properties: {
roleName: roleName
description: roleDescription
type: 'customRole'
assignableScopes: roleScopes
permissions: [
{
actions: resolvedActions
dataActions: permittedDataActions
}
]
}
}
resource roles 'Microsoft.Authorization/roleAssignments#2018-09-01-preview' = {
name: guid(subscription().id, 'bicep-roleassignments', 'dsddsd')
properties: {
principalId: userId.properties.principalId
roleDefinitionId: roleDef.id
}
}
Whenever I deploy this I need 2 runs. The first run ends in the error message:
Principal XXX does not exist in the directory YYY
where XXX would be a principal id the user-assigned identity has and YYY is my tenant id. If I now look into the portal the identity is created and XXX is the correct id.
So when I now simply re-run the deployment it works.
I consider it a bug in dependsOn which should relate to ARM templates and not Bicep. I could not find any place where I can report ARM template issues to Microsoft.
I'm asking to assure that I do not miss something else here.
Edit: Added complete working sample which shows the bug. To use it, copy the script content into a test.bicep locally. Then create a resource group (lets call it "rg-test"), ensure that your local POSH context is set correctly and execute the following line in the folder where you stored the bicep in:
New-AzResourceGroupDeployment -Name deploy -Mode Incremental -TemplateFile .\test.bicep -ResourceGroupName rg-test
In the role assignment, you need to specify the principalType to ServicePrincipal and also use an api version greater or equal than: 2018-09-01-preview.
When you create a service principal, it is created in an Azure AD. It takes some time for the service principal to be replicated globally. By setting the principalType to ServicePrincipal, it tells the ARM API t0 wait for the replication.
resource roles 'Microsoft.Authorization/roleAssignments#2018-09-01-preview' = {
name: guid(subscription().id, 'bicep-roleassignments', 'dsddsd')
properties: {
principalId: userId.properties.principalId
roleDefinitionId: roleDef.id
principalType: 'ServicePrincipal'
}
}
You need to reference a newly created identity inside identity property of the target resource. dependsOn is redundant because bicep creates resources in the correct order based on actual usage:
resource userId 'Microsoft.ManagedIdentity/userAssignedIdentities#2018-11-30' = {
name: 'myidentity'
location: resourceGroup().location
}
resource appService 'Microsoft.Web/sites#2021-02-01' = {
name: 'appserviceName'
location: resourceGroup().location
properties: {
//...
}
identity: {
type: 'UserAssigned'
userAssignedIdentities: {
'/subscriptions/{your_subscription_id}/resourceGroups/${resourceGroup().name}/providers/Microsoft.ManagedIdentity/userAssignedIdentities/${userId.name}': {}
}
}
}
The documentation doesn't recommend to use dependsOn without as strong reason:
In most cases, you can use a symbolic name to imply the
dependency between resources. If you find yourself setting explicit
dependencies, you should consider if there's a way to remove it.
So bicep does not require the dependsOn segment if referencing the property correctly.
Need to reference the properties.principalId of the userId in the resource block.
So would look like:
userId.properties.principalId
Here's a quickstart that calls out in a working example how this would work.

Add Azure Traffic Manager endpoints in main bicep template as child resource

I'm trying to add a new Traffic Manager Profile via a Network/trafficManagerProfiles.bicep module which I invoke in my main.bicep file.
This works well.
The main.bicep file is creating multiple Function Apps using their own little module, which is invoked similar like this
module functionAppModule 'Web/functions.bicep' = {
dependsOn: [
appServicePlanModule
webApiStorageAccount
]
name: 'functionAppModule'
params: {
environmentName: environmentName
systemName: systemName
azureRegion: azureRegion
appServicePlanId: appServicePlanModule.outputs.id
}
}
Now I'm trying to add the necessary endpoints of my web applications (Azure Functions) to the Traffic Manager Profile, which is also possible by using the endpoints property.
However, this would mean I need to add a parameter to this file accepting an array of objects containing all information about my App Services, or I would need to resolve them over here (by retrieving the instances with the existing keyword). This doesn't sound like the way to implement this, because all those resources are already available/referenced in the main.bicep file.
The Traffic Manager Profile module now looks like this:
param systemName string
#allowed([
'dev'
'test'
'acc'
'prod'
])
param environmentName string
param relativeLiveEndpoint string = '/api/Live'
var trafficManagerProfileName = '${systemName}${environmentName}'
resource trafficManagerProfile 'Microsoft.Network/trafficmanagerprofiles#2018-08-01' = {
name: trafficManagerProfileName
location: 'global'
properties: {
allowedEndpointRecordTypes: [
'DomainName'
]
dnsConfig: {
relativeName: trafficManagerProfileName
ttl: 60
}
profileStatus: 'Enabled'
trafficRoutingMethod: 'Performance'
monitorConfig: {
profileMonitorStatus: 'Online'
protocol: 'HTTPS'
port: 443
path: relativeLiveEndpoint
timeoutInSeconds: 10
intervalInSeconds: 30
toleratedNumberOfFailures: 3
}
endpoints: [
{
id: 'the resource id'
name: 'the resource name'
type: 'the resource type'
properties: {
endpointStatus: 'Enabled'
endpointMonitorStatus: 'Online'
targetResourceId: 'the resource id'
target: 'mysite.azurewebsites.net'
endpointLocation: 'West Europe'
weight: 1
priority: 1
}
}
// All other app services
]
}
}
According to some answers here on Stack Overflow, the endpoints can also be created via a child resource in ARM templates (something like Microsoft.Network/trafficmanagerprofiles/endpoints), however, this doesn't appear to be available in Bicep (or just isn't available in the autocomplete).
What I'd like to do is something like this in my main.bicep file, because that way I can reference all App Services I want to add.
var trafficManagerProfileEndpoints 'Microsoft.Network/trafficmanagerprofiles/endpoints#2050-01-01` = {
name: '${trafficManagerProfile.Name}/endpoints'
endpoints: [
// All of my endpoints
]
}
Somewhat similar compared to what's available via Microsoft.Web/sites/config#2020-12-01 for configuration settings in an App Service.
Any ideas on this?
I just ran through your question at lightning speed, forgive me if I don't fully understand it, but it looks like you need to declare your web app as a resource again, but as existing. This allows you to access all the props you need from your app service.
This blog explains how to use existing resources in Bicep: https://hexmaster.nl/posts/bicep-existing/
This is possible through Bicep ARM but the Microsoft.Network/trafficManagerProfiles api doesn't have types available for endpoints so it will generate a warning even if the deployment works perfectly.
If you define a traffic manager profile without the endpoints property:
resource trafficManager 'Microsoft.Network/trafficmanagerprofiles#2018-08-01' = {
name: '<name>'
location: 'global'
properties: {
profileStatus: 'Enabled'
trafficRoutingMethod: '<trafficRoutingMethod>'
dnsConfig: {
...
}
monitorConfig: {
...
}
}
}
You can then add endpoints like that:
// Azure endpoints: cloud service, app service, app service slots, public ip
resource trafficManagerAzureEndpoint 'Microsoft.Network/trafficManagerProfiles/azureEndpoints#2018-08-01' = {
name: '${trafficManagerName}/${name}'
properties: {
...
}
}
// External endpoint
resource trafficManagerExternalEndpoint 'Microsoft.Network/trafficManagerProfiles/externalEndpoints#2018-08-01' = {
name: '${trafficManagerName}/${name}'
properties: {
...
}
}
// Nested endpoints
resource trafficManagerNestedEndpoint 'Microsoft.Network/trafficManagerProfiles/nestedEndpoints#2018-08-01' = {
name: '${trafficManagerName}/${name}'
properties: {
...
}
}

Bicep Generate GUID

I am learning about bicep and how to use to deploy azure resources.
I have been working on key vault deployment as follow:
resource keyVault 'Microsoft.KeyVault/vaults#2021-06-01-preview' existing = {
name: keyVaultName
}
// Create key vault keys
resource keyVaultKeys 'Microsoft.KeyVault/vaults/keys#2021-06-01-preview' = [for tenantCode in tenantCodes: {
name: '${keyVault.name}/${keyVaultKeyPrefix}${tenantCode}'
properties: {
keySize: 2048
kty: 'RSA'
// storage key should only needs these operations
keyOps: [
'unwrapKey'
'wrapKey'
]
}
}]
what I would like to do now, is create a GUID for each deployment in this format for example:
b402c7ed-0c50-4c07-91c4-e075694fdd30
I couldn't find any source to achieve this.
Can anyone please direct me on the right path.
Thank you very much for any help and for your patience with a beginner
You can use the newGuid function, as per documentation:
Returns a value in the format of a globally unique identifier. This function can only be used in the default value for a parameter.
// parameter with default value
param deploymentId string = newGuid()
...
output deploymentId string = deploymentId

Resources