Contents
Last week we learned something about Azure Bicep user-defined data types, if you missed it you can check it here: Azure Bicep features you didn’t know about – Pt. 1. In this blog post we’ll have a look at another very helpful feature, user-defined functions in Azure Bicep.
Built-in functions
Built-in Bicep functions allow you to further automate and simplify your Bicep parameters and variables. I.e. you can combine arrays from multiple parameters into one using the union
function, get IP address range properties out of a CIDR range using the parseCidr
function, or enforce a maximum number of characters when passing a parameter to a resource deployment using the max
function (this is especially helpful when defining storage account names, which are limited in length).
User-defined functions
In Azure Bicep, user-defined functions allow you to define reusable pieces of code to simplify your templates and make them more maintainable. Here are some example use cases for user-defined functions in Azure Bicep:
- Abstracting resource properties: You can create functions to abstract away complex or repetitive configurations of Azure resources..
- Standardizing naming conventions: If you follow specific naming conventions for resources (hello governance), you can create a function to generate resource names according to those conventions. This ensures consistency across deployments and makes it easier to manage resources.
- Calculating default values: User-defined functions can calculate default values based on inputs from other parameters and variables. For instance, you could create a function to automatically set the sku name of a public IP based on the allocation method (a combination I usually get wrong every first try).
- Reusable code components: User-defined functions promote code reuse by allowing you to define a common logic once and reuse it in the same deployment file. This reduces duplication and simplifies maintenance efforts.
We’ll take a closer look at some code samples regarding examples 2 and 3 a bit later in this post, so stay with me.
How user-defined functions work
User-defined functions are declared directly in a *.bicep
file, usually in your main deployment file to only declare it once. The syntax is as follows:
func <user-defined-function-name> (<argument-name> <data-type>, <argument-name> <data-type>, ...) <function-data-type> => <expression>
The syntax is similar to other languages, where you initialize the function first using func
, specify a name for the function (<user-defined-function-name>
), input variables including their data types (<argument-name> <data-type>
), followed by the output data type (<function-data-type>
) and logic (<expression>
). It could look something like this:
func getSkuName(allocationMethod string) string => allocationMethod =~ 'Static' ? 'Standard' : 'Basic'
After that, you’re able to call the function in your resources like this: getSkuName('Static')
As of now, you can return the following data types:
- Array
- Bool
- Int
- Object
- String
Prerequisites
Because user-defined functions is still a preview feature, we have to enable it first by adding a few lines of code in our bicepconfig.json
file. If you haven’t heard of this configuration method you can find more information in my other blog post: 5 nifty Azure Bicep tips and tricks
We can simply start by creating an empty bicepconfig.json
file inside the root directory of our bicep deployment files. To solely enable the preview feature, add this section and save the file:
{ "experimentalFeaturesEnabled": { "userDefinedFunctions": true } }
Also, ensure you have installed Bicep CLI version 0.20.X or higher to use this feature. You can check your currently installed version with the command az bicep version
and upgrade using az bicep upgrade
.
Note: Since the enablement of the preview feature is based on the bicepconfig.json
file, you can enable the feature on a folder or project basis. In other words, you have to add this section to every bicepconfig.json
file in all the projects you want to use the feature.
Code examples
In the following example, we’ll take a look at a rather simple deployment of a public IP. You’ll find two user-defined functions at the top:
- getResourceName: Creates the resource name based on multiple inputs
- getSkuName: Sets the SKU name based on the allocation method
We then call the functions in lines 14 and 20:
// Functions func getResourceName(isProduction bool, resourceAbbreviation string, serviceName string) string => '${isProduction ? 'p' : 't'}-${resourceAbbreviation}-${serviceName}' func getSkuName(allocationMethod string) string => allocationMethod =~ 'Static' ? 'Standard' : 'Basic' // Parameters param location string = 'switzerlandnorth' param isProduction bool = true param resourceAbbreviation string = 'pip' param serviceName string = 'demo' param pipAllocationMethod string = 'Static' // Resources resource pip 'Microsoft.Network/publicIPAddresses@2023-09-01' = { name: getResourceName(isProduction, resourceAbbreviation, serviceName) location: location properties: { publicIPAllocationMethod: pipAllocationMethod } sku: { name: getSkuName(pipAllocationMethod) } }
Obviously, those are two pretty simple examples. You can really get creative using the different output data types and operators in combination with the built-in functions.
Limitations
Currently, there are some limitations, which might be fixed when the feature becomes GA:
- Functions can’t access variables, only parameters
- Functions can only use parameters that are defined in the function
- Parameters for the function can’t have default values
- Functions can’t use the reference function or any of the list functions
Resources
- User-defined functions in Bicep – Azure Resource Manager | Microsoft Learn
- Bicep functions – Azure Resource Manager | Microsoft Learn
- Bicep operators – Azure Resource Manager | Microsoft Learn
Conclusion
User-defined functions is another great feature to have in your toolbelt when it comes to going a step further in automation. The feature is still in preview but might become very powerful as the range of functions grows. You can find my example above including the bicepconfig.json file in my GitHub repository.
I hope this post helps you optimize your Bicep templates even further. Let me know if you have any further questions, cheers!