Bicep Template | Shared Variable File Pattern

Posted by Andrew Wilson on Wednesday, March 8, 2023

Background

I have recently been playing around with some of the Bicep functions when I came across a pattern by Microsoft called the Shared Variable File Pattern.

This nifty pattern describes a method in which you can extract what would either be commonly used or complex configuration values away from your Bicep Template. Using the pattern will allow you to retain easy to read and manageable Bicep templates where you have modelled large variable configurations and or configuration values that are used repeatedly across your templates (prevents multiple copies of the value that would need to be maintained).

How it works

Scenario | large configuration sets

Let’s assume we are creating an Azure API Management (APIM) resource that is internal (i.e. it can only be accessed within a virtual network, unless it is extended through other resources). Moving forward with our design, we have placed APIM in its own Subnet with a Network Security Group containing many inbound and outbound security rules. There is a minimum set of security rules that are required to ensure proper operation and access to our APIM instance.

Previously the method of modelling these in Bicep would be to add the configuration to a variable or parameter input. The configuration would look as follows:

	[
      // Inbound
      {
        name: 'AllowManagementEndpoint'
        properties: {
          description: 'Management endpoint for Azure portal and PowerShell'
          sourceAddressPrefix: 'ApiManagement'
          sourcePortRange: '*'
          destinationAddressPrefix: 'VirtualNetwork'
          destinationPortRange: '3443'
          protocol: 'Tcp'
          access: 'Allow'
          priority: 100
          direction: 'Inbound'
        }
      }
      {
        name: 'AllowAzureInfrastructureLoadBalancer'
        properties: {
          description: 'Azure Infrastructure Load Balancer'
          sourceAddressPrefix: 'AzureLoadBalancer'
          sourcePortRange: '*'
          destinationAddressPrefix: 'VirtualNetwork'
          destinationPortRange: '6390'
          protocol: 'Tcp'
          access: 'Allow'
          priority: 110
          direction: 'Inbound'
        }
      }
      // Outbound
      {
        name: 'AllowDependencyOnAzureStorage'
        properties: {
          description: 'Dependency on Azure Storage'
          sourceAddressPrefix: 'VirtualNetwork'
          sourcePortRange: '*'
          destinationAddressPrefix: 'Storage'
          destinationPortRange: '443'
          protocol: 'Tcp'
          access: 'Allow'
          priority: 120
          direction: 'Outbound'
        }
      }
      {
        name: 'AllowAccessToAzureSQLEndpoints'
        properties: {
          description: 'Access to Azure SQL endpoints'
          sourceAddressPrefix: 'VirtualNetwork'
          sourcePortRange: '*'
          destinationAddressPrefix: 'Sql'
          destinationPortRange: '1433'
          protocol: 'Tcp'
          access: 'Allow'
          priority: 130
          direction: 'Outbound'
        }
      }
      {
        name: 'AllowAccessToAzureKeyVault'
        properties: {
          description: 'Access to Azure Key Vault'
          sourceAddressPrefix: 'VirtualNetwork'
          sourcePortRange: '*'
          destinationAddressPrefix: 'AzureKeyVault'
          destinationPortRange: '443'
          protocol: 'Tcp'
          access: 'Allow'
          priority: 140
          direction: 'Outbound'
        }
      }
    ]

As can be seen in the example above, the values are static, the size of the configuration is sizeable and detracts away from the functioning parts of the Bicep template.

Solution

Using the Shared Variable File Pattern, let’s model the NSG rules in a Json file outside of our Bicep template.

Now that the configuration is outside of the Bicep template, the template already appears to be easier to maintain. However, we still need access to our configuration. To do this we are going to load the configuration into a variable in our Bicep template using the loadJsonContent Function.

var nsgRules = loadJsonContent('./nsgConfiguration.json')

If like me, you are using VS Code to create your Bicep templates, you will immediately notice that when you attempt to use your nsgRules variable there is intellisense when traversing the Json Object.

You can now easily assign your nsgRules to your Network Security Group, or conduct further work on the configuration before use, such as concatenating your minimum nsg rules with some custom ones that are dynamically created in the Bicep Template.

Multiple configuration components in a single file

If you are looking to keep a single configuration json file for your Bicep templates, but you do not want to keep loading the entire configuration into each template that requires it (there are template size limitations). There is a solution to this. The loadJsonContent function allows you to specify a jsonPath as the second parameter. This allows you to only load in parts of the configuration file that are applicable to the given template and its resource deployments. For example:

Configuration file apimConfiguration.json

{
	"ApimConfig":{
		...
	},
	"securityRules": [
		...
	]
}

Bicep template loading only securityRules


var nsgRules = loadJsonContent('./apimConfiguration.json', 'securityRules')

Note

I would recommend that you create multiple Json Shared Variable Files, each defined for logical aspects of your deployment. As mentioned at the start, the reason for doing any of this is to provide a manageable deployment solution. One file that contains all aspects to your deployment is not the dream.

Things to consider

As mentioned in the Microsoft Documentation there are the following considerations:

  • When you use this approach, the loaded JSON content will be included inside the ARM template generated by Bicep. The JSON ARM templates generated by Bicep have a file limit of 4MB

    • Either avoid using large shared variable files or only load in aspect that are required per template. If you are still hitting limits, you might need to consider splitting your deployment into multiple parts.
  • Ensure your shared variable arrays don’t conflict with the array values specified in each Bicep file. For example, when using the configuration set pattern to define network security groups, ensure you don’t have multiple rules that define the same priority and direction.

  • If you have various aspects of configuration for your deployment in a single Json file, look to separate these into different Json files each respective to the resource deployments that they will be used for. Such as:

    • commonConfig.json | configuration used commonly throughout your deployment templates.
    • apimConfig.json | configuration specificly used when deploying APIM resources.