Understand the "DeployIfNotExist" effect in Azure Policy
Tags: azure, iac
Published: 2024-03-20 21:54:07

Azure Policy is a tool to manage and enforce governance in your Cloud environment regardless if you have a few subscriptions or thousands of subscriptions. 
With Azure Policy you target a kind of resource, virtual machines, VNET… and apply an effect. You have a filter, targeting resources you want to govern and an effect, the action you want to perform when the policy hits resources. These actions are:

- Append: adds the defined set of fields to the resource
- Audit: generates a warning event in the activity log 
- AuditIfNotExists: generates a warning event in the activity log if a related resource doesn't exist
- Deny: fails the request based on the requested resource configuration
- DenyAction: fails the request based on the requested action (limited to delete action)
- DeployIfNotExists: deploys a related resource if it doesn't already exist.
- Disabled: doesn't evaluate resources for compliance with the policy 
- Modify: adds, updates, or removes the defined set of fields in the resource

You may know Audit, Deny, and DenyAction. These effects let you audit or deny a resource configuration because it isn’t aligned with the company rules, or because it can be seen as dangerous. But DeployIfNotExist effect is different, it allows a resource to be deployed with a configuration not compliant with your rules, and it deploys the desired configuration after the resource deployment.
An AuditIfNotExist Policy targets a type of resource, VM for example, and looks at existence conditions, if the VM has an extension for example, and if not an ARM template is executed in the same resource group as the resource. 
Let’s look at an example.
Let's say that your company uses an Azure Extension, and this extension is mandatory on all Windows VMs deployed in your tenant. You don’t want to block anyone who did not manage to modify their deployment stack by using a Deny Effect, instead, you want to add this extension automatically when people deploy an Ubuntu WM without it. 

First steps we need several elements. Because we need to deploy something we need a role ID, the role needed to perform the deployment. We need an identity, a user-managed identity and we need an ARM template. 
The role ID is Virtual Machine Contributor, the minimal role to interact with a VM (/providers/Microsoft.Authorization/roleDefinitions/9980e02c-c2be-4d73-94e8-173b1dc7cf3c)
The Arm template. 

```json
{
    "$schema": "http://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
    "contentVersion": "1.0.0.0",
    "parameters": {
       "vmName": {
          "type": "string"
       },
       "location": {
          "type": "string"
       }
    },
    "resources": [
       {
          "name": "[concat(parameters('vmName'),'/extension')]",
          "type": "Microsoft.Compute/virtualMachines/extensions",
          "location": "[parameters('location')]",
          "apiVersion": "2017-12-01",
          "properties": {
             "publisher": "ExtensionPublisher",
             "type": "ExtensionType",
             "typeHandlerVersion": "1.0",
             "autoUpgradeMinorVersion": true,
             "settings": { },
             "protectedSettings": { }
          }
       }
    ] 
 }
```

This template simply deploys the extension on a VM using two parameters: The VMName and the VM Location. 
Now we can build the policy. The policy needs to target Windows Server VM So the IF section of the policy rule should look like this.

```json
          "if": {
             "allOf": [
                {
                   "field": "type",
                   "equals": "Microsoft.Compute/virtualMachines"
                },
                {
                   "field": "Microsoft.Compute/imagePublisher",
                   "equals": "MicrosoftWindowsServer"
                },
                {
                   "field": "Microsoft.Compute/imageOffer",
                   "equals": "WindowsServer"
                },
                {
                   "field": "Microsoft.Compute/imageSKU",
                   "in": [
                      "2008-R2-SP1",
                      "2008-R2-SP1-smalldisk",
                      "2012-Datacenter",
                      "2012-Datacenter-smalldisk",
                      "2012-R2-Datacenter",
                      "2012-R2-Datacenter-smalldisk",
                      "2016-Datacenter",
                      "2016-Datacenter-Server-Core",
                      "2016-Datacenter-Server-Core-smalldisk",
                      "2016-Datacenter-smalldisk",
                      "2016-Datacenter-with-Containers",
                      "2016-Datacenter-with-RDSH"
                   ]
                }
             ]
          }
```

Here, with the AllOf the policy filter resources that are virtual machines, with MicrosoftWindowsServer as image publisher and WindowsServer as Image Offer, then the policy filter by SKU.
After the If section you will have the Then section with the effect deployIfNotExist


```json
 "then": {
             "effect": "deployIfNotExists",
             "details": {
                "type": "Microsoft.Compute/virtualMachines/extensions",
                "existenceCondition": {
                   "allOf": [
                      {
                         "field": "Microsoft.Compute/virtualMachines/extensions/type",
                         "equals": "ExtensionType"
                      },
                      {
                         "field": "Microsoft.Compute/virtualMachines/extensions/publisher",
                         "equals": "ExtensionPublisher"
                      }
                   ]
                },
                "roleDefinitionIds": [
                   "/providers/Microsoft.Authorization/roleDefinitions/92aaf0da-9dab-42b6-94a3-d43ce8d16293"
                ],
```

When writing a deployIfNotExist clause, a details property, optional, is added here,  this property indicates to the policy engine what to look for in resources filtered by the If clause. Here we are looking for a virtual machine extension with the property described in the existenceCondition clause. 
After that, we define the role ID needed to perform the deployment operation, Virtual Machine Contributor. 
Finally, there is the deployment property with the ARM template created before

```json
               "deployment": {
                   "properties": {
                      "mode": "incremental",
                      "template": {
                        "$schema": "http://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
                        "contentVersion": "1.0.0.0",
                        "parameters": {
                           "vmName": {
                              "type": "string"
                           },
                           "location": {
                              "type": "string"
                           }
                        },
                        "resources": [
                           {
                              "name": "[concat(parameters('vmName'),'/extension')]",
                              "type": "Microsoft.Compute/virtualMachines/extensions",
                              "location": "[parameters('location')]",
                              "apiVersion": "2017-12-01",
                              "properties": {
                                 "publisher": "ExtensionPublisher",
                                 "type": "ExtensionType",
                                 "typeHandlerVersion": "1.0",
                                 "autoUpgradeMinorVersion": true,
                                 "settings": { },
                                 "protectedSettings": { }
                              }
                           }
                        ] 
                     },
                      "parameters": {
                         "vmName": {
                            "value": "[field('name')]"
                         },
                         "location": {
                            "value": "[field('location')]"
                         }
                      }
                   }
                }
```

 The deployment property includes a mode property, to indicate if the ARM template should be deployed using the complete mode or the incremental mode. Generally, you should choose the incremental mode. 
Then comes the template itself and finally a parameters section where template parameters are filed, for the value can be filed with the value of the field found by the if section of the policy. 

The complete policy

```json
{
    "properties": {
       "displayName": "Deploy default Log Analytics VM Extension for Windows VMs.",
       "mode": "Indexed",
       "description": "This policy deploys Log Analytics VM Extensions on Windows VMs, and connects to the selected Log Analytics workspace.",
       "metadata": {
          "category": "Compute"
       },
       "parameters": {
          "logAnalytics": {
             "type": "String",
             "metadata": {
                "displayName": "Log Analytics workspace",
                "description": "Select Log Analytics workspace from dropdown list",
                "strongType": "omsWorkspace"
             }
          }
       },
       "policyRule": {
          "if": {
             "allOf": [
                {
                   "field": "type",
                   "equals": "Microsoft.Compute/virtualMachines"
                },
                {
                   "field": "Microsoft.Compute/imagePublisher",
                   "equals": "MicrosoftWindowsServer"
                },
                {
                   "field": "Microsoft.Compute/imageOffer",
                   "equals": "WindowsServer"
                },
                {
                   "field": "Microsoft.Compute/imageSKU",
                   "in": [
                      "2008-R2-SP1",
                      "2008-R2-SP1-smalldisk",
                      "2012-Datacenter",
                      "2012-Datacenter-smalldisk",
                      "2012-R2-Datacenter",
                      "2012-R2-Datacenter-smalldisk",
                      "2016-Datacenter",
                      "2016-Datacenter-Server-Core",
                      "2016-Datacenter-Server-Core-smalldisk",
                      "2016-Datacenter-smalldisk",
                      "2016-Datacenter-with-Containers",
                      "2016-Datacenter-with-RDSH"
                   ]
                }
             ]
          },
          "then": {
             "effect": "deployIfNotExists",
             "details": {
                "type": "Microsoft.Compute/virtualMachines/extensions",
                "existenceCondition": {
                   "allOf": [
                      {
                         "field": "Microsoft.Compute/virtualMachines/extensions/type",
                         "equals": "ExtensionType"
                      },
                      {
                         "field": "Microsoft.Compute/virtualMachines/extensions/publisher",
                         "equals": "ExtensionPublisher"
                      }
                   ]
                },
                "roleDefinitionIds": [
                   "/providers/Microsoft.Authorization/roleDefinitions/92aaf0da-9dab-42b6-94a3-d43ce8d16293"
                ],
                "deployment": {
                   "properties": {
                      "mode": "incremental",
                      "template": {
                        "$schema": "http://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
                        "contentVersion": "1.0.0.0",
                        "parameters": {
                           "vmName": {
                              "type": "string"
                           },
                           "location": {
                              "type": "string"
                           }
                        },
                        "resources": [
                           {
                              "name": "[concat(parameters('vmName'),'/extension')]",
                              "type": "Microsoft.Compute/virtualMachines/extensions",
                              "location": "[parameters('location')]",
                              "apiVersion": "2017-12-01",
                              "properties": {
                                 "publisher": "ExtensionPublisher",
                                 "type": "ExtensionType",
                                 "typeHandlerVersion": "1.0",
                                 "autoUpgradeMinorVersion": true,
                                 "settings": { },
                                 "protectedSettings": { }
                              }
                           }
                        ] 
                     },
                      "parameters": {
                         "vmName": {
                            "value": "[field('name')]"
                         },
                         "location": {
                            "value": "[field('location')]"
                         }
                      }
                   }
                }
             }
          }
       }
    }
 }
```