Use a Parameter to Assign User Assigned Managed Identities to Resources With Bicep
Isn’t that title a mouthful.
Coming from Terraform, there are somethings that seem strange in Bicep.
One of those is the way that the Resource Manager API handles assigning User Assigned Managed Identities (UAMIs).
If you look at the API documentation for a resource
(in this case we are going to use an Event Hub Namespace, but this applies to all resources that can have a UAMI assigned)
you will see that the userAssignedIdentities
value of the identity
property looks lkie this:
identity: {
type: 'string'
userAssignedIdentities: {
{customized property}: {}
}
}
What this means is we need to supply an object to the property where the key is the ID of the UAMI wrapped in braces ({}
) and the value is an empty object.
In Terraform, we can simply provide an array of objects and it handles the rest.
So how can we replicate this behaviour in a Bicep module?
Enter Bicep lambda functions.
Lambda functions allow us to create a lambda expression to perform actions on an object or array.
Common uses of lambda functions are to filter, map, or reduce an array, or to transform one into an object.
In this case, we are going to use a lambda function to transform an array of UAMI IDs into the object that the API expects.
As mentioned above, we are going to use an Event Hub Namespace as an example.
param userAssignedIdentities array = []
var suffix = uniqueString(resourceGroup().id, deployment().name)
resource eventHubNamespace 'Microsoft.EventHub/namespaces@2024-05-01-preview' = {
name: 'uami-evh-${suffix}'
location: deployment().location
sku: {
name: 'Standard'
tier: 'Standard'
}
identity: length(userAssignedIdentities) > 0
? {
type: 'SystemAssigned, UserAssigned'
userAssignedIdentities: reduce(userAssignedIdentities, {}, (result, id) => union(result, { '${id}': {} }))
}
: {
type: 'SystemAssigned'
}
}
After declaring the userAssignedIdentities
parameter, we create a new Event Hub Namespace.
When constructing the identity
property, we first check if any userAssignedIdentities
are provided.
If we have any, we then use the reduce
function to ‘reduce’ the array into an object.
The three arguments to the reduce
function are the array to reduce, the initial value of the result object, and the lambda function to apply to each element.
In the lambda function, we declare two variables: result
which stores the current state of the result object, and id
which is the current element of the array (in our case the resource ID of the UAMI).
We then create a new object, and use the union
function to merge our constructed object into the result
object.
In my situation, I needed to also include a couple of common identities - one for the encryption key, and the other for publishing metrics to Log Analytics. My updated code looks like this:
resource eventHubNamespace 'Microsoft.EventHub/namespaces@2024-05-01-preview' = {
name: 'uami-evh-${suffix}'
location: deployment().location
sku: {
name: 'Standard'
tier: 'Standard'
}
identity: {
type: 'SystemAssigned, UserAssigned'
userAssignedIdentities: reduce(
flatten([
[
'/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg/providers/Microsoft.ManagedIdentity/userAssignedIdentities/EncryptionId'
'/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg/providers/Microsoft.ManagedIdentity/userAssignedIdentities/MonitoringId'
]
userAssignedIdentities
]),
{},
(curr, id) => union(curr, { '${id}': {} })
)
}
}
The only differences here are the removal of the length check on the userAssignedIdentities
parameter,
and the addition of the flatten
function to combine the common identities with the user supplied ones.