Conditional deployment in Azure Bicep

omiossec

Olivier Miossec

Posted on October 31, 2024

Conditional deployment in Azure Bicep

Modularity is a key when designing and coding Infrastructure as Code. You need to generalize and be flexible to reuse the code with different types of environments and situations.

Imagine a situation where you need to deploy resources in different Azure regions and environments (prod and dev). Each region and each environment has its specificities, some resources can be deployed in one but not the other, and some properties can change between regions or environments.

Let’s take a simple but concrete case: you need to deploy a VNET with two subnets. But in a production environment, you want to have a NAT Gateway, and in North Europe and France Central, one of the subnets should be private. Nat Gateway and private subnet aren't needed in dev, but you still need to deploy two subnets.

Creating a Bicep file deploying a Nat Gateway with its public IP, a VNET, and two subnets with the NAT Gateway ID, one of which is a private subnet (defaultOutboundAccess: false in Bicep).
But how can we deploy or not a resource based on one parameter?

Bicep has two options.

The first one is conditional deployment. You must add an if and an expression before the resource definition to use it.

resource logicalName 'resource@2023-07-01' = if (condition) {
  xxxxxx
} 
Enter fullscreen mode Exit fullscreen mode

If the expression “Condition” is true, Bicep will deploy the resource, if not the resource is not deployed.

Here, we must deploy a Nat Gateway if prod environments only exist in North Europe and France Central.

The condition looks like

param environment string  

environment == 'prod' && (location == 'francecentral' || location == 'northeurope')
Enter fullscreen mode Exit fullscreen mode

We will need to use the condition on several resources, so it is wise to put it in a variable. It simplifies the code, especially here, with the complex evaluation using several logical operators see

var natGateWayDeployBool = environment == 'prod' && (location == 'francecentral' || location == 'northeurope')
Enter fullscreen mode Exit fullscreen mode

Resources can be deployed using:

var natGateWayDeployBool = environment == 'prod' && (location == 'francecentral' || location == 'northeurope')

resource publicip 'Microsoft.Network/publicIPAddresses@2024-01-01' = if (natGateWayDeployBool) {
  name: publicipname
  location: location
  sku: {
    name: 'Standard'
  }
  properties: {
    publicIPAddressVersion: 'IPv4'
    publicIPAllocationMethod: 'Static'
    idleTimeoutInMinutes: 4
  }
}


resource natgateway 'Microsoft.Network/natGateways@2024-01-01' =  if (natGateWayDeployBool)  {
  name: natGatewayName
  location: location
  sku: {
    name: 'Standard'
  }
  properties: {
    idleTimeoutInMinutes: 4
    publicIpAddresses: [
      {
        id: publicip.id
      }
    ]
  }
}
Enter fullscreen mode Exit fullscreen mode

The Nat Gateway and its Public IP are deployed only if the environment is prod and the location is North Europe and France Central.

Now, how to deal with subnets?

In production, in North Europe and France Central subnet looks like this:

resource subnet1 'Microsoft.Network/virtualNetworks/subnets@2024-03-01' = {
  parent: vnet
  name: subnet1Name
  properties: {
    addressPrefix: subnet1Prefix
    natGateway: {
      id: natgateway.id
    }
  }
}

resource subnet2 'Microsoft.Network/virtualNetworks/subnets@2024-03-01' = {
  parent: vnet
  name: subnet2Name

  properties: {
    addressPrefix: subnet2Prefix
    defaultOutboundAccess: false
    natGateway: {
      id: natgateway.id
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

But for production in West Europe, we should remove the natGateway reference. For the dev environment, we should not have the natGatteway reference, and the defaultOutboundAccess property should be true.

First, let’s create a variable for the defaultOutboundAccess property it should be false for production and true for dev. The value should be false for the dev environment so we could use an ! (logical NOT in Bicep)

var defaultOutboundAccessBool = !(environment == 'prod')
Enter fullscreen mode Exit fullscreen mode

The second subnet looks like:

resource subnet2 'Microsoft.Network/virtualNetworks/subnets@2024-03-01' = {
  parent: vnet
  name: subnet2Name

  properties: {
    addressPrefix: subnet2Prefix
    defaultOutboundAccess: defaultOutboundAccessBool 
    natGateway: {
      id: natgateway.id
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

We also need to remove the NatGateway Reference for dev and West Europe. The natGateWayDeployBool will be useful here.
If expression is not available inside a resource, only at the resource level, only the conditional expression ?: is permitted.

The form is simple
condition ? true-value : false-value

The natGateway property isn't a simple string, it asks for an expression, but you can use an expression with ?:

natGateway: (natGateWayDeployBool ? { id: natgateway.id } : null)

The subnet resources:

resource subnet1 'Microsoft.Network/virtualNetworks/subnets@2024-01-01' =  {
  parent: vnet
  name: subnet1Name
  properties: {
    addressPrefix: subnet1Prefix
    natGateway: (natGateWayDeployBool ?  { id: natgateway.id } : null)
  }
}

resource subnet2 'Microsoft. Network/virtualNetworks/subnets@2024-01-01' = {
  parent: vnet
  name: subnet2Name

  properties: {
    addressPrefix: subnet2Prefix
    defaultOutboundAccess: defaultOutboundAccessBool 
    natGateway: (natGateWayDeployBool ?  { id: natgateway.id  } : null)
  }
}
Enter fullscreen mode Exit fullscreen mode

This is a simple example of what you can do with conditional deployment in Azure Bicep.

The full bicep code

param location string = resourceGroup().location

@description('VNet name')
param vnetName string 

@description('Vnet Address space')
param vnetAddressPrefix string 

@description('Subnet1 Prefix')
param subnet1Prefix string 

@description('Subnet1 Name')
param subnet1Name string  

@description('Subnet2 Prefix')
param subnet2Prefix string  

@description('Subnet2 Name')
param subnet2Name string 


@description('Environment Type')
@allowed([
  'dev'
  'prod'
])
param environment string  


@description('Public IP Nqme')
param publicipname string  = 'pibIPNqt'


@description('Nat Gateway Name')
param natGatewayName string   = 'NattGw'

var natGateWayDeployBool = environment == 'prod' && (location == 'francecentral' || location == 'northeurope')

var defaultOutboundAccessBool = !(environment == 'prod')


resource publicip 'Microsoft.Network/publicIPAddresses@2024-01-01' = if (natGateWayDeployBool) {
  name: publicipname
  location: location
  sku: {
    name: 'Standard'
  }
  properties: {
    publicIPAddressVersion: 'IPv4'
    publicIPAllocationMethod: 'Static'
    idleTimeoutInMinutes: 4
  }
}


resource natgateway 'Microsoft.Network/natGateways@2024-01-01' =  if (natGateWayDeployBool)  {
  name: natGatewayName
  location: location
  sku: {
    name: 'Standard'
  }
  properties: {
    idleTimeoutInMinutes: 4
    publicIpAddresses: [
      {
        id: publicip.id
      }
    ]
  }
}



resource vnet 'Microsoft.Network/virtualNetworks@2024-01-01' = {
  name: vnetName
  location: location
  properties: {
    addressSpace: {
      addressPrefixes: [
        vnetAddressPrefix
      ]
    }

  }
}


resource subnet1 'Microsoft.Network/virtualNetworks/subnets@2024-01-01' =  {
  parent: vnet
  name: subnet1Name
  properties: {
    addressPrefix: subnet1Prefix
    natGateway: (natGateWayDeployBool ?  { id: natgateway.id } : null)
  }
}


resource subnet2 'Microsoft.Network/virtualNetworks/subnets@2024-01-01' = {
  parent: vnet
  name: subnet2Name

  properties: {
    addressPrefix: subnet2Prefix
    defaultOutboundAccess: defaultOutboundAccessBool 
    natGateway: (natGateWayDeployBool ?  { id: natgateway.id  } : null)
  }
}
Enter fullscreen mode Exit fullscreen mode
💖 💪 🙅 🚩
omiossec
Olivier Miossec

Posted on October 31, 2024

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related