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.
Cool, I didn’t realise ARM had termination protection like AWS!
Going to update a couple of my templates now :)
LikeLiked by 1 person