azureazure-bicep

Pass function app principal id to another module


I have a bicep template that has a number of resources defined in param arrays, one for storage accounts, another for function apps and so on. It then calls the resource specific modules, loops through the array for that specific resource and creates the resources.

I now need to do some role assigments on a storage account, using the principal id from a function app. I am trying to pass the list of function app principal ids to the role assignment module, but no matter what I try I am hitting an issue.

Here is what I currently have. I am trying to pass the output array of the function app pricipal ids to the storageRbac module, and the reference the id using an indexer inside the storageRbac module, but this doesn't work. If I try and reference the output funcAppsPrincId I get the error 'Does not exist in this context'.

main.bicep

param storageAccounts array = [
  // Output Index 0
  { 
    name: 'staccount01'
    sku: {
      name: 'Standard_LRS'
      tier: 'Standard'
    }
    ...
  }
  // Output Index 1
  { 
    name: 'staccount02'
    sku: {
      name: 'Standard_LRS'
      tier: 'Standard'
    }
    ...
  }]

param funcApps array = [
  // Index 0
  { 
    name: 'funcApp01'
    kind: 'functionapp,linux'
    ...
  }
  // Index 1
  { 
    name: 'funcApp02'
    kind: 'functionapp,linux'
    ...
  }]

module deployStorageAccount 'storageAccount.bicep' = [for storageAccount in storageAccounts: {
  scope: resourceGroup(rlrFeRg)
  name: storageAccount.name
  params: {
    ...
  }
}]

module deployFuncApps 'functionApp.bicep' = [for funcApp in funcApps: {
  scope: resourceGroup(rlrFeRg)
  name: funcApp.name
  params: {
    ...
  }
  dependsOn: [
    deployStorageAccount
  ]
}]

output funcAppsPrincId array = [for (funcApp, i) in funcApps: [
  deployFuncApps[i].outputs.funcAppPrincipalId
]]

module storageRoleAssignment 'storageRbac.bicep' = {
  name: 'storageAccountRbac'
  params: {
    rlrAdGroup: rlrAdGroup
    storageAccounts: storageAccounts
    funcAppsPrincipalIds: funcAppsPrincId
  }
  dependsOn: [
    deployStorageAccount
    deployFuncApps
  ]
}

functionApp.bicep

output funcAppPrincipalId string = functionApp.identity.principalId

storageRbac.bicep

param devsAdGroup string
param storageAccounts array
param funcAppsPrincipalIds array

var storageRoleAssignments = [
  {
    storageAccountName: storageAccounts[0].name
    principalId: funcAppsPrincipalIds[0]
    roleDefinition: 'Storage Blob Data Contributor'
    principalType: 'ServicePrincipal'
  }
  {
    storageAccountName: storageAccounts[0].name
    principalId: funcAppsPrincipalIds[1]
    roleDefinition: 'Storage Table Data Contributor'
    principalType: 'ServicePrincipal'
  }
  {
    storageAccountName: storageAccounts[1].name
    principalId: devsAdGroup
    roleDefinition: 'Storage Blob Data Contributor'
    principalType: 'Group'
  }
  {
    storageAccountName: storageAccounts[1].name
    principalId: devsAdGroup
    roleDefinition: 'Storage Table Data Contributor'
    principalType: 'Group'
  }
]

module storageRoleAssignment 'rbacStorageAccount.bicep' = [for storageRoleAssignment in storageRoleAssignments: {
  name: 'rbac-${storageRoleAssignment.storageAccountName}'
  params: {
    storageAccountName: storageRoleAssignment.storageAccountName
    principalId: storageRoleAssignment.principalId
    roleDefinition: storageRoleAssignment.roleDefinition
    principalType: storageRoleAssignment.principalType
  }
}]

I can pass each individual principal id as a parameter like below, but ideally I'd like to pass the whole array to the storageRbac module and use the indexer there. Is there any way to achieve this?

main.bicep

module storageRoleAssignment 'storageRbac.bicep' = {
  name: 'storageAccountRbac'
  params: {
    rlrAdGroup: rlrAdGroup
    storageAccounts: storageAccounts
    funcAppsPrincipalId1: deployFuncApps[0].outputs.funcAppPrincipalId
    funcAppsPrincipalId2: deployFuncApps[1].outputs.funcAppPrincipalId
  }
  dependsOn: [
    deployStorageAccount
    deployFuncApps
  ]
}

Solution

  • You could use an intermediary module to deploy your functions: functionApps.bicep

    param funcApps array = []
    
    module deployFuncApps 'functionApp.bicep' = [
      for funcApp in funcApps: {
        scope: resourceGroup()
        name: funcApp.name
        params: {
          functionAppName: funcApp.name
        }
      }
    ]
    
    output funcAppsPrincIds array = [
      for (funcApp, i) in funcApps: [
        deployFuncApps[i].outputs.funcAppPrincipalId
      ]
    ]
    

    So from your main.bicep file it could look like that:

    module deployFuncApps 'functionApps.bicep' = {
      scope: resourceGroup()
      name: 'deploy-function-apps'
      params: {
        funcApps: funcApps
      }
      dependsOn: [
        deployStorageAccount
      ]
    }
    
    module storageRoleAssignment 'storageRbac.bicep' = {
      name: 'storageAccountRbac'
      params: {
        ...
        funcAppsPrincipalIds: deployFuncApps.outputs.funcAppsPrincIds
      }
      dependsOn: [
        deployStorageAccount
        deployFuncApps
      ]
    }