Azure Policy: Append multiple tags

Howdy, here is an example of the custom Azure Policy that is based on Append policy action that automatically adds additional fields to the requested resource during creation or update. A common example is adding tags on resources such as costCenter or specifying allowed IPs for a storage resource. This policy appends specified tags and values on resources, so you can easily group them in order to get their consumption and costs, for example. Although the policy has default tags name and values, you can provide your own during the policy assignment.

TIP: Different versions of json files for PowerShell and deploying policy with Portal are available in my Github repo

This sample is for deploying policy with Azure Portal:

{
    "mode": "Indexed",
    "parameters": {
        "tagName1": {
            "type": "String",
            "metadata": {
                "displayName": "Tag Name1",
                "description": "Name of the tag, such as 'environment'"
            },
            "defaultValue": "environment"
        },
        "tagValue1": {
            "type": "String",
            "metadata": {
                "displayName": "Tag Name1 Value",
                "description": "Value of the tag, such as 'production'"
            },
            "defaultValue": "production"
        },
        "tagName2": {
            "type": "String",
            "metadata": {
                "displayName": "Tag Name2",
                "description": "Name of the tag, such as 'service'"
            },
            "defaultValue": "service"
        },
        "tagValue2": {
            "type": "String",
            "metadata": {
                "displayName": "TagName2 Value",
                "description": "Value of the tag, such as 'webapps'"
            },
            "defaultValue": "webapps"
        },
        "tagName3": {
            "type": "String",
            "metadata": {
                "displayName": "Tag Name3",
                "description": "Name of the tag, such as 'project'"
            },
            "defaultValue": "project"
        },
        "tagValue3": {
            "type": "String",
            "metadata": {
                "displayName": "Tag Name3 Value",
                "description": "Value of the tag, such as 'POC'"
            },
            "defaultValue": "POC"
        }
    },
    "policyRule": {
        "if": {
            "allOf": [
                {
                    "field": "[concat('tags[', parameters('tagName1'), ']')]",
                    "exists": "false"
                },
                {
                    "field": "[concat('tags[', parameters('tagName2'), ']')]",
                    "exists": "false"
                },
                {
                    "field": "[concat('tags[', parameters('tagName3'), ']')]",
                    "exists": "false"
                }
            ]
        },
        "then": {
            "effect": "append",
            "details": [
                {
                    "field": "[concat('tags[', parameters('tagName1'), ']')]",
                    "value": "[parameters('tagValue1')]"
                },
                {
                    "field": "[concat('tags[', parameters('tagName2'), ']')]",
                    "value": "[parameters('tagValue2')]"
                },
                {
                    "field": "[concat('tags[', parameters('tagName3'), ']')]",
                    "value": "[parameters('tagValue3')]"
                }
            ]
        }
    }
}

To assign the policy by using PowerShell:

# Create the Policy Definition (Subscription scope)
$policyrules = "https://raw.githubusercontent.com/rlevchenko/stuff/master/Azure/Policy/PS/appendtags-rules.json"
$policyparams = "https://raw.githubusercontent.com/rlevchenko/stuff/master/Azure/Policy/PS/appendtags-parameters.json"
$definition = New-AzPolicyDefinition -Name 'Append Multiple Tags' -Policy $policyrules  -Parameter $policyparams -Mode Indexed

# Set the scope to a resource group; may also be a resource, subscription, or management group
$scope = Get-AzResourceGroup -Name 'mvphero'

# Create the Policy Assignment
New-AzPolicyAssignment -Name 'Apply multiple tags' -DisplayName 'Apply tags and their default values' -Scope $scope.ResourceId -PolicyDefinition $definition

And this is how it looks from Azure Portal during the policy assignment:

The tags added by policy on NSG resource:

Building Windows images with Packer

Hi, folks!

Sometimes you need to create a base or custom image to use one in any kind of automated deployments (CD pipelines, Dev, QA  and etc.) in cloud or on-premises environments. Then, you might start searching for a good solution to make your task easier. Built-in sysprep?  Well, it’s a classic way for Windows without any additional functionality that might be required especially for clouds. So, what can be used for such task?  Definitely, Packer from HashiCorp would be one of the best tool. It allows you to build your custom image from Marketplace image (as for example) and place that image to the Azure Images for further usage.

In the JSON-example below, Packer uses provided options for authentication (variable section) and passes them to the Azure Resource Manager builder section. Packer supports a bunch of builders such as Azure, Hyper-V, VMware or AWS . In my case, Packer uses Azure RM and it’s Windows Server 2019-Datacenter marketplace image, creates a VM, connects to the VM via communicator (see communicator subsection), and then prepares image by running scripts and actions defined in the provisioners section.  I’m using here two PowerShell scripts for installing IIS role and OS sysprepping at the end of customization. Also, packer automatically updates OS and restarts it if necessary (custom windows-update and built-in windows-restart provisioners)

{
    "variables": {
        "client_id": "service principal|id here",
        "client_secret": "service principal| secret here",
        "tenant_id": "AD tenant's id here",
        "subscription_id": "subscription's id here"
    },
    "builders": [
        {
            "type": "azure-arm",
            "client_id": "{{user `client_id`}}",
            "client_secret": "{{user `client_secret`}}",
            "tenant_id": "{{user `tenant_id`}}",
            "subscription_id": "{{user `subscription_id`}}",
            "os_type": "Windows",
            "image_publisher": "MicrosoftWindowsServer",
            "image_offer": "WindowsServer",
            "image_sku": "2019-Datacenter",
            "image_version": "latest",
            "managed_image_resource_group_name": "TestRG",
            "managed_image_name": "ws2019-iis",
            "disk_caching_type": "ReadWrite",
            "communicator": "winrm",
            "winrm_use_ssl": true,
            "winrm_insecure": true,
            "winrm_timeout": "20m",
            "winrm_username": "packer",
            "location": "West Europe",
            "vm_size": "Standard_A2_v2",
            "azure_tags": {
                "dept": "IT"
            }
        }
    ],
    "provisioners": [
        {
            "type": "powershell",
            "inline": [
                "Write-Host 'Configuring IIS Role and sysprepping...'"
            ]
        },
        {
            "type": "powershell",
            "script": "./scripts/iis-install.ps1"
        },
        {
            "type": "windows-update"
        },
        {
            "type": "windows-restart"
        },
        {
            "type": "powershell",
            "script": "./scripts/iis-sysprep.ps1"
        }
    ]
}

When you end up with the configuration file, run packer build and wait while customization steps finish. Packer’s basic steps for a build are:

  • Create a resource group.
  • Validate and deploy a VM template.
  • Execute provision – defined by the user; typically shell commands.
  • Power off and capture the VM.
  • Delete the resource group.
  • Delete the temporary VM’s OS disk.

As a result, image with the name defined in the managed_image_name option will be added to Azure Images service:

packer azure images

Scripts and other stuff will be available on my GitHub soon. Stay tuned.