Bicep Flexibility & Reusability

Bicep templates are powerful because of their flexibility and reusability. You can add flexibility to your Bicep templates by using parameters, variables, and extensions.

Parameters

A parameter lets you bring values in from outside of the Bicep template file. For example, you can bring in names of resources that need to be unique, locations to deploy the resources into, settings that affect the pricing of resources, like their SKUs, pricing tiers, and instance counts, and credentials and information needed to access other systems that aren’t defined in the template.

You can define a parameter like this.

You define a parameter in Bicep by using the param key word to tell Bicep that we will define a parameter. That’s followed by the name of the parameter that you’re defining. Like the symbolic name, this is only shown within the Bicep file. Users deploying this Bicep template may have to know what the parameter name is so they can define the parameter value at the time of deployment. In this example, the parameter name is appServiceAppName. Your parameter names should be clear and understandable to whoever is going to be deploying the Bicep file. We also use the parameter name to refer to this parameter from other resources in the Bicep template. Then you define the type of parameter you’re defining. This is the type of information we’re expecting this parameter to contain. In this example it’s a string, which means it will be expecting some text. You can also define an integer which would be a number, boolean which is a true or false value, and you can also pass in more complex parameters using arrays and objects.

You can also set a hard-coded default value for a parameter, but this is optional. If you don’t specify a hard-coded default value then whoever is deploying the Bicep template will have to define the value at the point of deployment. If you provide a default value you’re making the parameter optional to whoever will be deploying the Bicep template. We’re basically saying, if there’s no value defined then use this hard-coded value instead. You can define the hard-coded default value by just using the = sign.

Once you’ve defined the parameter you can refer to it throughout the rest of the Bicep file. You can do that by just referencing the parameter name in a resources properties. In this example, we can specify the name of the App Service App as our parameter name and this give the value defined in the parameter or defined by whoever deploys the Bicep template and defines the parameter value.

Variables

Variables are very similar to parameters, they’re defined and set in the Bicep template and let you store important information in one place rather than scattered throughout the template, allowing you to reuse that value without having to copy and paste it each time. This is great if you want to use the same value in your deployment but need to refer to it several different times or if you want to use an express to create a complex value.

So to define a variable you use the var key word first

You give the variable a symbolic name, like you do with a parameter, so you can reference the variable throughout your template and then provide a value for the variable. Unlike with parameters though you have to define a value for the variable in your template, you can’t define variables at the point of deployment. You don’t need to specify a type in a variable, unlike in a parameter, as Intellisense can get the type from the value you specify. Again, make sure you’re using clear, descriptive, and consistent names they’re easy to read and understand, both for yourself and for anyone else on your team who wants to reuse your code.

So when should you use a parameter over a variable or vice versa. Parameters are used when anyone deploying your Bicep template might need to override the value. So for instance, if they need to change the name of resources or specify a pricing tier. Variables are a good idea when you need the value multiple times within your Bicep file, but you don’t need that to come in from an external source. So if you were defining the name of a resource that doesn’t need to be unique you can use a variable for that.

Expressions

Expressions you can use with both parameters and variables. When you’re writing templates, you often don’t want to hard-code values or even ask for them to be specified in a parameter. Expressions can compute information dynamically. For example, you might want to say that we want to deploy all our resources in the same location that the resource group is located in. Or you could use more complex expressions to create unique names for resources based on having globally unique names following along with a naming convention your company uses.

So to expand on the expression to use the location of the resource group for the location of the resource, you would use the below expression.

This expression gives the parameter value the Azure region that the resource group is located in. This expression uses a function, in this example the resource group function and is expressed as resourceGroup(). And in this case we’re using the location property of this resource group function. There are other properties of the resource group function that you can use and I’ll going through those in another blog. You can still override this hard-coded default value by specify the location parameter at the point of deployment.

You may have noticed when you’re writing your Bicep templates that Visual Studio Code gives tips about how best to write your Bicep code. For example, when you hard-code a location in your Bicep template it will display a yellow squiggly line under it. This is to indicate that it’s best practice not to set a resource location as hard-coded in your template. If you hover over the location value it will display this tip.

Visual Studio Code is recommending us to use a parameter or an expression to define the location rather than hard coding it.

So what we can do, is use the location parameter defined earlier which uses the resource group location expression, rather than hard-coding the value. By using the location parameter Visual Studio Code won’t show this warning.

Another expression you can use is the uniqueString() expression which can be used to provide globally unique names for resources. Instead of making the globally unique name the problem of the person deploying your Bicep template you can use the below expression as a value for a storage account name.

The uniqueString() function is really handy when you’re creating unique resource names. It takes a seed value, and basically means that when you provide the same seed value, you get the same value back. But if you provide a different seed value, you’ll get a different value back. In the example above, we’re using the resourceGroup().id as the seed value. This provides the resource ID of the resource group as the seed.

The resource group ID is a really good value to use as the seed because anytime we use it we get the same seed value. If we changed the resource group name, the resource group ID will change. Again, if we chose a resource group with the same name but was in a different subscription the resource group ID is different. That’s really covenant because if we deploy in different places we get a different value, but if we deploy over and over again from the same place we get the same value. Meaning we don’t get any issues recreating our resources.

The unique names provided by this function aren’t very meaningful or descriptive so what you can do is use string interpolation to concatenate the names into a more meaning name that potentially complies with your naming convention.

The above example uses toylaunch as the hard-coded string that helps anyone looking at the deployed resource in Azure to understand what this storage account is for. The ${uniqueString(resourceGroup().id)} is a way of telling Bicep to evaluate the output of the uniqueString(resoureGroup().id) fuction, and then concatenate it into the string. This allows you to use both a meaningful and unique name. Some resources don’t allow their name to start with a number, so if you use this method you won’t run into that issue.

Output

It’s common when you’re deploying a Bicep template, either automatically with a CI/CD pipeline or manually, that you want to send some data back into a pipeline or made visible to the human who is deploying the template. In Bicep we can achieve this by using an output. Outputs are a way for your Bicep code to send data back to whoever or whatever started the deployment.

So similar to how you would define a parameter, you use the key word output, a symbolic name, a type, and a value.

Some example scenarios where you might need to get information from the template deployment are:

  • You create a Bicep template that deploys a virtual machine, and you need to get the public IP address so that you can SSH into the machine.
  • You create a Bicep template that accepts a set of parameters, like an environment name and an application name. The template uses an expression to create the name of an Azure App Service app that it deploys.

You need to output the name pf the app that the template has deployed so that it can be used within a deployment pipeline to publish the application binaries.

Outputs are displayed in the Azure portal and anyone who has read access to the resource group you’re deploying to can view the outputs so be careful with what you output.

Never output a secret or any usernames or passwords as these will potentially be viewable to a lot of people.

Modules

It’s often a good idea to break-up your Bicep code up into smaller, more readable, and manageable chunks to make it more modularized. You can create individual Bicep files, called modules, for different parts of your deployments and make the code more reusable. The main Bicep template can reference these modules. Behind the scenes, modules are transpiled into a single JSON template for deployment.

So, Bicep modules enable you to organise and reuse your Bicep code by creating smaller units that can be composed into a template.

If you were creating some networking resources for solution A, and solution B had the same networking resources, you can reuse the same module for both solutions.

A module declaration is similar to a resource declaration. It uses the key word module to declare it as a module.

And also has a symbolic name like when declaring a resource.

But instead of a resource type and API version, you’ll reference the file by name.

Like when declaring a resource, the name is a mandatory field when declaring a module.

You can declare any parameters the module uses by declaring the params key word.

When you set the values for the parameters in the main template you can use other parameters, expressions, properties of other resources deployed within the template, and outputs from other modules. Any Bicep will automatically understand these dependencies.

Just like with templates, Bicep modules can define outputs as well. The output from one module can be used as a parameter in another module. By using modules and outputs together you can create very powerful and reusable Bicep files.

Each module should have a clear purpose, you can use modules to define all of the resources that are related to a specific part of your solution. For example, you might create a module that contains all of the resources for networking and another module for your monitoring resources. But don’t put every resource into it’s own module. In general, it’s better for modules to combine multiple resources.

A module should have clear parameters and outputs that make sense to the people deploying the template. A module should also be as self-contained as possible. If the module needs to use a variable to define a part of the module, the variable should generally be included in the module file rather than in the parent template. Also make sure your module doesn’t output any secrets it’s using, like connection strings or keys.

About the author