Oops . . . did I delete that VM?

Accidents happen. Resource Locks help prevent them.

The Resource Lock level is applied at the resource group or resource scope. These can be set by the administrators can be set to CanNotDelete.

Using a modified version of the ARM Template from a post on creating a CentOS Virtual Machine, let’s provision a VM that is protected it from accidental deletion. The best thing about creating locks in ARM Templates, is that it centralizes the configuration. It makes it easier to maintain and simplifies our workflow.

As a best practice, we should consider creating Resource Locks for mission critical resources in our Azure Resource Manager (ARM) Templates.

The Azure Resource Manager (ARM) Template

The Virtual Machine Resource Lock part of the template looks like this, and is inserted in the Virtual Machine Resource.

"resources": [{
                     "type": "Microsoft.Compute/virtualMachines/providers/locks",
                     "name": "[concat(variables('vmName'), '/Microsoft.Authorization/','VirtualMachine')]",
                     "apiVersion": "2015-01-01",
                     "dependsOn": [ "[concat('Microsoft.Compute/virtualMachines/', variables('vmName'))]" ],
                     "properties": {
                     "level": "CannotDelete",
                     "notes": "Do not delete!"
                     }
             }]

To make things easy to follow, both data disks and the Custom Script for Linux Virtual Machine extension were removed from the ARM Template.

{
    "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
    "contentVersion": "1.0.0.0",
    "parameters": {
        "newStorageAccountName": {
            "type": "string",
            "metadata": {
                "description": "Unique DNS Name for the Storage Account where the Virtual Machine's disks will be placed."
            }
        },
        "adminUsername": {
            "type": "string",
            "metadata": {
                "description": "User name for the Virtual Machine."
            }
        },
        "adminPassword": {
            "type": "securestring",
            "metadata": {
                "description": "Password for the Virtual Machine."
            }
        },
        "dnsNameForPublicIP": {
            "type": "string",
            "metadata": {
                "description": "Unique DNS Name for the Public IP used to access the Virtual Machine."
            }
        },
        "OSVersion": {
            "type": "string",
            "defaultValue": "7.1",
            "allowedValues": [
                "7.1"
            ]
        }
    },
    "variables": {
        "addressPrefix": "10.0.0.0/16",
        "dataDiskSize": "100",
        "imageOffer": "CentOS",
        "imagePublisher": "OpenLogic",
        "location": "West US",
        "nicName": "myVMNic",
        "OSDiskName": "osdisk",
        "publicIPAddressType": "Dynamic",
        "storageAccountType": "Standard_LRS",
        "subnetName": "Subnet",
        "subnetPrefix": "10.0.0.0/24",
        "subnetRef": "[concat(variables('vnetID'),'/subnets/',variables('subnetName'))]",
        "virtualNetworkName": "MyVNET",
        "vmName": "msbriseboislinux",
        "vmSize": "Standard_A1",
        "vmStorageAccountContainerName": "vhds",
        "vnetID": "[resourceId('Microsoft.Network/virtualNetworks',variables('virtualNetworkName'))]"
    },
    "resources": [
        {
            "type": "Microsoft.Storage/storageAccounts",
            "name": "[parameters('newStorageAccountName')]",
            "apiVersion": "2015-05-01-preview",
            "location": "[variables('location')]",
            "tags": {
                "displayName": "StorageAccount"
            },
            "properties": {
                "accountType": "[variables('storageAccountType')]"
            }
        },
        {
            "apiVersion": "2015-05-01-preview",
            "type": "Microsoft.Network/publicIPAddresses",
            "name": "[parameters('dnsNameForPublicIP')]",
            "location": "[variables('location')]",
            "tags": {
                "displayName": "PublicIPAddress"
            },
            "properties": {
                "publicIPAllocationMethod": "[variables('publicIPAddressType')]",
                "dnsSettings": {
                    "domainNameLabel": "[parameters('dnsNameForPublicIP')]"
                }
            }
        },
        {
            "apiVersion": "2015-05-01-preview",
            "type": "Microsoft.Network/virtualNetworks",
            "name": "[variables('virtualNetworkName')]",
            "location": "[variables('location')]",
            "tags": {
                "displayName": "VirtualNetwork"
            },
            "properties": {
                "addressSpace": {
                    "addressPrefixes": [
                        "[variables('addressPrefix')]"
                    ]
                },
                "subnets": [
                    {
                        "name": "[variables('subnetName')]",
                        "properties": {
                            "addressPrefix": "[variables('subnetPrefix')]"
                        }
                    }
                ]
            }
        },
        {
            "apiVersion": "2015-05-01-preview",
            "type": "Microsoft.Network/networkInterfaces",
            "name": "[variables('nicName')]",
            "location": "[variables('location')]",
            "tags": {
                "displayName": "NetworkInterface"
            },
            "dependsOn": [
                "[concat('Microsoft.Network/publicIPAddresses/', parameters('dnsNameForPublicIP'))]",
                "[concat('Microsoft.Network/virtualNetworks/', variables('virtualNetworkName'))]"
            ],
            "properties": {
                "ipConfigurations": [
                    {
                        "name": "ipconfig1",
                        "properties": {
                            "privateIPAllocationMethod": "Dynamic",
                            "publicIPAddress": {
                                "id": "[resourceId('Microsoft.Network/publicIPAddresses',parameters('dnsNameForPublicIP'))]"
                            },
                            "subnet": {
                                "id": "[variables('subnetRef')]"
                            }
                        }
                    }
                ]
            }
        },
        {
            "apiVersion": "2015-05-01-preview",
            "type": "Microsoft.Compute/virtualMachines",
            "name": "[variables('vmName')]",
            "location": "[variables('location')]",
            "tags": {
                "displayName": "VirtualMachine"
            },
            "dependsOn": [
                "[concat('Microsoft.Storage/storageAccounts/', parameters('newStorageAccountName'))]",
                "[concat('Microsoft.Network/networkInterfaces/', variables('nicName'))]"
            ],
            "resources": [{
                     "type": "Microsoft.Compute/virtualMachines/providers/locks",
                     "name": "[concat(variables('vmName'), '/Microsoft.Authorization/','VirtualMachine')]",
                     "apiVersion": "2015-01-01",
                     "dependsOn": [ "[concat('Microsoft.Compute/virtualMachines/', variables('vmName'))]" ],
                     "properties": {
                     "level": "CannotDelete",
                     "notes": "Do not delete!"
                     }
             }
             ],
            "properties": {
                "hardwareProfile": {
                    "vmSize": "[variables('vmSize')]"
                },
                "osProfile": {
                    "computername": "[variables('vmName')]",
                    "adminUsername": "[parameters('adminUsername')]",
                    "adminPassword": "[parameters('adminPassword')]"
                },
                "storageProfile": {
                    "imageReference": {
                        "publisher": "[variables('imagePublisher')]",
                        "offer": "[variables('imageOffer')]",
                        "sku": "[parameters('OSVersion')]",
                        "version": "latest"
                    },
                    "osDisk": {
                        "name": "osdisk",
                        "vhd": {
                            "uri": "[concat('http://',parameters('newStorageAccountName'),'.blob.core.windows.net/',variables('vmStorageAccountContainerName'),'/',variables('OSDiskName'),'.vhd')]"
                        },
                        "caching": "ReadWrite",
                        "createOption": "FromImage"
                    },
                    "dataDisks": [
                        {
                            "name": "datadisk1",
                            "diskSizeGB": "[variables('dataDiskSize')]",
                            "lun": 0,
                            "vhd": {
                                "Uri": "[concat('http://',parameters('newStorageAccountName'),'.blob.core.windows.net/vhds/',variables('vmName'),'dataDisk1' ,'.vhd')]"
                            },
                            "caching": "None",
                            "createOption": "Empty"
                        },
                        {
                            "name": "datadisk2",
                            "diskSizeGB": "[variables('dataDiskSize')]",
                            "lun": 1,
                            "vhd": {
                                "Uri": "[concat('http://',parameters('newStorageAccountName'),'.blob.core.windows.net/vhds/',variables('vmName') ,'dataDisk2','.vhd')]"
                            },
                            "caching": "None",
                            "createOption": "Empty"
                        }
                    ]
                },
                "networkProfile": {
                    "networkInterfaces": [
                        {
                            "id": "[resourceId('Microsoft.Network/networkInterfaces',variables('nicName'))]"
                        }
                    ]
                }
            }
        }
    ]
}

Template Parameters

{
    "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#",
    "contentVersion": "1.0.0.0",
    "parameters": {
        "newStorageAccountName": {
            "value": "mslinuxbrisebois"
        },
        "adminUsername": {
            "value": "brisebois"
        },
        "dnsNameForPublicIP": {
            "value": "briseboisdns"
        }
    }
}

Deploying the Template to Azure

To deploy the ARM Template, navigate to the folder that contains both the template and parameter files. Then call the New-AzureResourceGroup cmdlet.

Switch-AzureMode AzureResourceManager

New-AzureResourceGroup -Name 'mslinuxbrisebois' `
                       -Location 'westus' `
                       -TemplateFile 'DeploymentTemplate.json' `
                       -TemplateParameterFile 'DeploymentTemplate.param.dev.json' `
                       -Force -Verbose

# Result

VERBOSE: 4:09:48 PM - Created resource group 'mslinuxbrisebois' in location 'westus'
VERBOSE: 4:09:49 PM - Template is valid.
VERBOSE: 4:09:50 PM - Create template deployment 'DeploymentTemplate'.
VERBOSE: 4:09:58 PM - Resource Microsoft.Storage/storageAccounts 'mslinuxbrisebois' provisioning status is running
VERBOSE: 4:10:06 PM - Resource Microsoft.Network/virtualNetworks 'MyVNET' provisioning status is running
VERBOSE: 4:10:06 PM - Resource Microsoft.Network/publicIPAddresses 'briseboisdns' provisioning status is running
VERBOSE: 4:10:19 PM - Resource Microsoft.Network/virtualNetworks 'MyVNET' provisioning status is succeeded
VERBOSE: 4:10:19 PM - Resource Microsoft.Network/publicIPAddresses 'briseboisdns' provisioning status is succeeded
VERBOSE: 4:10:22 PM - Resource Microsoft.Network/networkInterfaces 'myVMNic' provisioning status is succeeded
VERBOSE: 4:10:25 PM - Resource Microsoft.Storage/storageAccounts 'mslinuxbrisebois' provisioning status is succeeded
VERBOSE: 4:10:30 PM - Resource Microsoft.Compute/virtualMachines 'msbriseboislinux' provisioning status is running
VERBOSE: 4:12:11 PM - Resource Microsoft.Compute/virtualMachines 'msbriseboislinux' provisioning status is succeeded
VERBOSE: 4:12:14 PM - Resource Microsoft.Compute/virtualMachines/providers/locks 'msbriseboislinux/Microsoft.Authorization/VirtualMachine' provisioning status is succeeded

Removing the Resource Lock & Deleting the Resource Group

Deleting a Resource Group that contains Resources Lock requires us to start by removing all Locks. First we need to find the locks.

# Getting details about the lock
Get-AzureResourceLock -ResourceGroupName 'mslinuxbrisebois' `
                      -ResourceName 'msbriseboislinux' `
                      -ResourceType 'Microsoft.Compute/virtualMachines'

# Result

Name                  : VirtualMachine
ResourceId            : /subscriptions/4db19144-ce4d-4dfa-b2c9-043b75b9290/resourcegroups/mslinuxbrisebois/providers/Microsoft.Compute/virtualMachines/
                        msbriseboislinux/providers/Microsoft.Authorization/locks/VirtualMachine
ResourceName          : msbriseboislinux
ResourceType          : Microsoft.Compute/virtualMachines
ExtensionResourceName : VirtualMachine
ExtensionResourceType : Microsoft.Authorization/locks
ResourceGroupName     : mslinuxbrisebois
SubscriptionId        : 4db19144-ce4d-4dfa-b2c9-043b75b92907
Properties            :

Then using the lock Resource ID, we can break the lock and then we can use the Remove-AzureResourceGroup cmdlet to delete the Resource Group.

# Deleting the lock
Remove-AzureResourceLock -ResourceId '/subscriptions/4db19144-ce4d-4dfa-b2c9-043b75b9290/resourcegroups/mslinuxbrisebois/providers/Microsoft.Compute/virtualMachines/msbriseboislinux/providers/Microsoft.Authorization/locks/VirtualMachine' `
                         -Force

# Deleting the Resource Group
Remove-AzureResourceGroup -Name 'mslinuxbrisebois' -Force -Verbose

If you’ve been following my recent posts about the Azure Resource Manager, you may have noticed that I changed the way that I deleted the resource lock. I did this because I ran into some issues with the code from my earlier post. Using the Lock Resource ID worked like a charm for this scenario.

Additional Resources

One response to Locking a Virtual Machine Resource – Azure Resource Manager Template

  1. 

    Cool, I didn’t realise ARM had termination protection like AWS!

    Going to update a couple of my templates now :)

    Liked by 1 person

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.