dynamically add a user assigned managed identity to a function app during DevOps Deployment - azure-resource-manager

I am creating ARM templates to deploy an azure function.
I can parameterize most things, but I am unable to parameterize UserAssignedManagedIdentities.
The value for a userAssignedManagedIdentity is not a value in the json ARM template, but is instead an object that contains the reference to the managed identity.
ARM does not allow parameterization in this instance.
I can hard code the reference to the managed identity but this is not ideal when deploying across multiple environments.
Is there a way to dynamically add a user assigned managed identity to a function app during Azure Devops Deployment?
This does not work
This works
Update: Adding more detail.
Below is the section of the template, parameterized, and the error that I get.
"resources": [
{
"type": "Microsoft.Web/sites",
"apiVersion": "2022-03-01",
"name": "[parameters('FunctionAppName')]",
"location": "North Europe",
"kind": "functionapp",
"identity": {
"type": "SystemAssigned, UserAssigned",
"userAssignedIdentities": {
"[concat('/subscriptions/', parameters('SubscriptionID'), '/resourcegroups/', parameters('ResourceGroup'),'/providers/Microsoft.ManagedIdentity/userAssignedIdentities/', parameters('UserAssignedManagedEntityName') ]": {}
}
},
"properties": {
"enabled": true,
"hostNameSslStates": [
{
"name": "[concat(parameters('FunctionAppName'),'.azurewebsites.net')]",
"sslState": "Disabled",
"hostType": "Standard"
},

Related

ARM FunctionApp listkeys retrieve previous custom key

We've got ARM deployment template which is working fine, the only issue there is, as we're creating FunctionApp and adding custom key to it that will be referenced in API Management to connect API with FunctionApp backend. It looks like, even with dependsOn in backend resource in template referencing to FunctionKeys resource, listkeys() still fetches one from before deployment. Has anyone faced this scenario and is there anything in particular I'm missing? Or is dependsOn thinking that the key has been deployed already, and even tho future deployments update the key, backend isn't actually waiting for the completion of FunctionKeys resource deployment?
Template snippets:
backends
{
"type": "Microsoft.ApiManagement/service/backends",
"apiVersion": "2018-01-01",
"name": "[concat(parameters('apiManagementServiceName'), '/', variables('functionName'))]",
"dependsOn": [
"[resourceId('Microsoft.ApiManagement/service', parameters('apiManagementServiceName'))]",
"[resourceId('Microsoft.Web/sites', variables('functionName'))]",
"[resourceId('Microsoft.Web/sites/host/functionKeys', variables('functionName'),'default','apimanagement')]"
],
"properties": {
"url": "[concat('https://', variables('functionName'), '.azurewebsites.net/api')]",
"protocol": "http",
"resourceId": "[concat('https://management.azure.com/subscriptions/', subscription().subscriptionId, '/resourceGroups/', resourceGroup().name, '/providers/Microsoft.Web/sites/', variables('functionName'))]",
"credentials": {
"header": {
"x-functions-key": [
"[listkeys(concat(variables('functionAppId'), '/host/default/'),'2016-08-01').functionKeys.apimanagement]"
]
}
}
}
}
functionKeys
{
"type": "Microsoft.Web/sites/host/functionKeys",
"apiVersion": "2018-11-01",
"name": "[concat(variables('functionName'), '/default/apimanagement')]",
"dependsOn": [
"[resourceId('Microsoft.Web/sites', variables('functionName'))]"
],
"properties": {
"name": "apimanagement"
}
},
The listKeys call is scheduled too early... In a greenfield scenario the deployment would fail, in brownfield you "get the old key" instead of the new one. It's a "limitation" in the template engine that you can work around. Basically, you need to nest the deployment that uses the listKeys function (i.e. your backend resource). There's a little bit of detail on it here in the Use a Nested Deployment section

Landing Zone Automation - Disable Owner requirement from ESLZ ARM template

I am trying to deploy ESLZ Arm template in this link "https://github.com/Azure/Enterprise-Scale/blob/main/docs/reference/adventureworks/README.md" and it requires owner permission to do that. Is it possible to remove the Global Admin and/or Owner requirement and run the template using contributor role
I created a management group under tenant root and assigned contributor role. Now I'm trying to create additional management groups using below ARM template
{
"$schema": "https://schema.management.azure.com/schemas/2019-08-01/tenantDeploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"topLevelManagementGroupPrefix": {
"type": "string",
"metadata": {
"description": "Provide prefix for the management group structure."
}
},
"platformMgs": {
"type": "array",
"defaultValue": [
"management",
"connectivity",
"identity"
],
"metadata": {
"description": "Management groups for platform specific purposes, such as management, networking, identity etc."
}
},
"landingZoneMgs": {
"type": "array",
"defaultValue": [
"online",
"corp"
],
"metadata": {
"description": "These are the landing zone management groups."
}
}
},
"variables": {
"enterpriseScaleManagementGroups": {
"ESLZ": "[concat(parameters('topLevelManagementGroupPrefix'))]",
"platform": "[concat(parameters('topLevelManagementGroupPrefix'), '-', 'platform')]"
}
},
"resources": [
{
// Create management group for platform management groups
"type": "Microsoft.Management/managementGroups",
"apiVersion": "2020-05-01",
"scope": "/",
"name": "[variables('enterpriseScaleManagementGroups').platform]",
"properties": {
"displayName": "[variables('enterpriseScaleManagementGroups').platform]",
"details": {
"parent": {
"id": "[tenantResourceId('Microsoft.Management/managementGroups/', parameters('topLevelManagementGroupPrefix'))]"
}
}
}
}
],
"outputs": {}
}
While deploying the template i'm getting permission error, however able to create management group manually. Am I missing something in this template. Any help is really appreciated
You should be able to be able to deploy the template with contributor permissions at the tenant level... You need an owner/userAccessAdmin/global admin to grant those permissions.

Exception in function does not return exceptions on functions in function monitor

The azure function is a .net core class library that will receive the message based on the namespace of the model being sent (in the filter as eventType) as an . All deployments are being done using arm templates, which is where this struggle is originating from. The function and eventgrid are deployed fine, but I don't know what i'm doing wrong with the subscription. If I create the subscription in the portal then the handler receives the message and displays traffic on the monitor. If I create the subscription as below then it appears exactly the same in the portal as the portal created one but nothing shows up in the monitor. Am I missing a resource or connection that still needs to be created? I read about system topics and how they're made implicitly in some instances but can be made explicitly, is that what I'm missing? This would be easier to debug if there was a place to export the template for those subscriptions but I don't see them.
Function handler
[FunctionName("FunctionName")]
public async Task Run([EventGridTrigger]EventGridEvent eventGridEvent)
{
...
}
}
eventgrid creation
{
"type": "Microsoft.EventGrid/topics",
"apiVersion": "2020-06-01",
"name": "[variables('EventGridName')]",
"location": "[resourceGroup().location]"
}
subscription creations
{
"name": "[concat(variables('eventSubscriptions')[copyIndex()].eventGridName, '/Microsoft.EventGrid/', variables('eventSubscriptions')[copyIndex()].name)]",
"type": "Microsoft.EventGrid/topics/providers/eventSubscriptions",
"apiVersion": "2020-01-01-preview",
"location": "[resourceGroup().location]",
"copy": {
"name": "subscriptionCopy",
"count": "[length(variables('eventSubscriptions'))]"
},
"properties": {
"topic": "[concat('/subscriptions/', subscription().subscriptionId,'/resourcegroups/', resourceGroup().name, '/providers/Microsoft.EventGrid/topics/', variables('eventSubscriptions')[copyIndex()].eventGridName)]",
"destination": {
"endpointType": "AzureFunction",
"properties": {
"resourceId": "[concat('/subscriptions/', subscription().subscriptionId,'/resourcegroups/', resourceGroup().name, '/providers/Microsoft.Web/sites/', variables('eventSubscriptions')[copyIndex()].functionApp, '/functions/' , variables('eventSubscriptions')[copyIndex()].functionName)]",
"maxEventsPerBatch": 1,
"preferredBatchSizeInKilobytes": 64
}
},
"filter": {
"includedEventTypes": [
"[variables('eventSubscriptions')[copyIndex()].eventType]"
]
},
"labels": [],
"eventDeliverySchema": "EventGridSchema"
},
"dependsOn": [
]
}

Conditionally add site property using ARM templates

I have a situation where I sometimes want my site to have a hostNameSslStates property. I have been trying to figure out how to do this without having seperate deployments for sites with a hostNameSslState and sites without.
This is how I would add a site today, I then add slots, config, certifcates, roles etc so the total ARM file is much larger.
{
"name": "[parameters('appServiceName')]",
"type": "Microsoft.Web/sites",
"apiVersion": "2018-11-01",
"location": "[parameters('location')]",
"tags": {
"displayName": "AppService"
},
"identity": {
"type": "SystemAssigned"
},
"properties": {
"serverFarmId": "[resourceId('Microsoft.Web/serverfarms', parameters('appServicePlanName'))]",
"siteConfig": "[variables('siteConfig')]",
"hostNameSslStates": [
{
"name": "[parameters('websiteCustomDomainName')]",
"sslState": "SniEnabled",
"thumbprint": "[reference(resourceId('Microsoft.Web/certificates', variables('certificateName'))).Thumbprint]",
"toUpdate": true
}
]
}
}
The problem is that some app services does not have a custom domain and does not need a hostNameSslStates property and I am not able to find a solution to how to implement this in the template. I can have a variable that is "hostNameSslState" but I am not able to condition the property. If I could specify hostNameSslStates as a seperate resource then that would solve my issue but I have not been able to figure it out.
you can do this:
"hostNameSslStates": "[if(something, variables('sslStuff'), json('[]'))]"
and then in your variables define sslStuff:
"sslStuff": [
{
"name": "[parameters('websiteCustomDomainName')]",
"sslState": "SniEnabled",
"thumbprint": "[reference(resourceId('Microsoft.Web/certificates', variables('certificateName'))).Thumbprint]",
"toUpdate": true
}
}

How do I access the server farm resource id for a web app from within linked ARM template files?

I've got a master ARM deployment file with these resources:
{
"apiVersion": "2015-01-01",
"name": "SharedServicePlanTemplate",
"type": "Microsoft.Resources/deployments",
"properties": {
"templateLink": { "uri": "[concat(variables('templateBase'), 'serviceplan.template.json')]" },
"parametersLink": { "uri": "[concat(variables('parametersBase'), 'serviceplan.shared.json')]" },
"mode": "Incremental"
}
},
{
"name": "my_website",
"type": "Microsoft.Web/sites",
"location": "[resourceGroup().location]",
"apiVersion": "2015-08-01",
"dependsOn": [
"[resourceId('Microsoft.Web/serverfarms', 'ServicePlanShared')]"
],
"tags": {
"[concat('hidden-related:', resourceGroup().id, '/providers/Microsoft.Web/serverfarms/', 'ServicePlanShared')]": "Resource",
"displayName": "my_website"
},
"properties": {
"name": "my_website",
"serverFarmId": "[resourceId('Microsoft.Web/serverfarms', 'ServicePlanShared')]"
}
}
When I try to deploy, I get the following error:
New-AzureRmResourceGroupDeployment : InvalidTemplate: Deployment template validation failed: 'The resource
'Microsoft.Web/serverfarms/ServicePlanShared' is not defined in the template.
I thought that was the whole reason for using the resourceId function, though. I can merge my serviceplan.template.json and the website resource into the same template file, but I'd rather not do that since I will have multiple websites using that plan, and I want to be able to deploy them separately.
Change your dependsOn property to:
"dependsOn" : ["SharedServicePlanTemplate"]
One gotcha with your nested approach is if the name of your service plan changes in the linked parameters file, the resource won't be found. Passing that in as a parameter (whether you use the linked parameters file or pass it through) might be a better approach. A bit orthogonal but something to think about.

Resources