#!/usr/bin/python # # Copyright (c) 2020 Suyeb Ansari (@suyeb786), Pallavi Chaudhari(@PallaviC2510) # # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) from __future__ import absolute_import, division, print_function __metaclass__ = type DOCUMENTATION = \ ''' --- module: azure_rm_backupazurevm version_added: '1.1.0' short_description: Back up an Azure Virtual Machine using Azure Backup description: - Back up an Azure VM using Azure Backup. - Enabling/Updating protection for the Azure VM. - Trigger an on-demand backup for a protected Azure VM. - Stop protection but retain existing data. - Stop protection and delete data. options: resource_group: description: - The name of the resource group. required: true type: str recovery_vault_name: description: - The name of the Azure Recovery Service Vault. required: true type: str resource_id: description: - Azure Virtual Machine Resource ID. required: true type: str backup_policy_id: description: - Backup Policy ID present under Recovery Service Vault mentioned in recovery_vault_name field. required: true type: str recovery_point_expiry_time: description: - Recovery Point Expiry Time in UTC. - This used if C(state) parameter is C(backup). required: false type: str version_added: '1.15.0' state: description: - Assert the state of the protection item. - Use C(create) for enabling protection for the Azure VM. - Use C(update) for changing the policy of protection. - Use C(stop) for stop protection but retain existing data. - Use C(delete) for stop protection and delete data. - Use C(backup) for on-demand backup. default: create type: str choices: - create - update - delete - stop - backup extends_documentation_fragment: - azure.azcollection.azure - azure.azcollection.azure_tags author: - Suyeb Ansari (@suyeb786) - Pallavi Chaudhari (@PallaviC2510) ''' EXAMPLES = \ ''' - name: Enabling/Updating protection for the Azure VM azure_rm_backupazurevm: resource_group: 'myResourceGroup' recovery_vault_name: 'testVault' resource_id: '/subscriptions/00000000-0000-0000-0000-000000000000/ \ resourceGroups/myResourceGroup/providers/Microsoft.Compute/virtualMachines/testVM' backup_policy_id: '/subscriptions/00000000-0000-0000-0000-000000000000/ \ resourceGroups/myResourceGroup/providers/microsoft.recoveryservices/vaults/testVault/backupPolicies/ProdPolicy' state: 'create' - name: Stop protection but retain existing data azure_rm_backupazurevm: resource_group: 'myResourceGroup' recovery_vault_name: 'testVault' resource_id: '/subscriptions/00000000-0000-0000-0000-000000000000/ \ resourceGroups/myResourceGroup/providers/Microsoft.Compute/virtualMachines/testVM' state: 'stop' - name: Stop protection and delete data azure_rm_backupazurevm: resource_group: 'myResourceGroup' recovery_vault_name: 'testVault' resource_id: '/subscriptions/00000000-0000-0000-0000-000000000000/ \ resourceGroups/myResourceGroup/providers/Microsoft.Compute/virtualMachines/testVM' state: 'delete' - name: Trigger an on-demand backup for a protected Azure VM azure_rm_backupazurevm: resource_group: 'myResourceGroup' recovery_vault_name: 'testVault' resource_id: '/subscriptions/00000000-0000-0000-0000-000000000000/ \ resourceGroups/myResourceGroup/providers/Microsoft.Compute/virtualMachines/testVM' backup_policy_id: '/subscriptions/00000000-0000-0000-0000-000000000000/ \ resourceGroups/myResourceGroup/providers/microsoft.recoveryservices/vaults/testVault/backupPolicies/ProdPolicy' recovery_point_expiry_time: '2023-02-09T06:00:00Z' state: 'backup' ''' RETURN = \ ''' id: description: - VM backup protection details. returned: always type: str sample: '{"response":{"id":"protection_id","name":"protection_item_name","properties":{}}}' ''' from ansible_collections.azure.azcollection.plugins.module_utils.azure_rm_common_rest import GenericRestClient from ansible_collections.azure.azcollection.plugins.module_utils.azure_rm_common_ext import AzureRMModuleBaseExt import json class Actions: (NoAction, Create, Update, Delete) = range(4) class BackupAzureVM(AzureRMModuleBaseExt): def __init__(self): self.module_arg_spec = dict( resource_group=dict( type='str', required=True ), recovery_vault_name=dict( type='str', required=True ), resource_id=dict( type='str', required=True ), backup_policy_id=dict( type='str', required=True ), recovery_point_expiry_time=dict( type='str' ), state=dict( type='str', default='create', choices=['create', 'update', 'delete', 'stop', 'backup'] ) ) self.resource_group = None self.recovery_vault_name = None self.resource_id = None self.backup_policy_id = None self.recovery_point_expiry_time = None self.state = None self.results = dict(changed=False) self.mgmt_client = None self.url = None self.status_code = [200, 201, 202, 204] self.to_do = Actions.NoAction self.body = {} self.query_parameters = {} self.query_parameters['api-version'] = None self.header_parameters = {} self.header_parameters['Content-Type'] = 'application/json; charset=utf-8' super(BackupAzureVM, self).__init__(derived_arg_spec=self.module_arg_spec, supports_check_mode=True, supports_tags=True) def get_api_version(self): return '2019-05-13' if self.state == 'create' or self.state == 'update' or self.state == 'delete' or self.state == 'stop' else '2016-12-01' def get_url(self): sub_id = self.subscription_id if self.module.params.get('subscription_id'): sub_id = self.module.params.get('subscription_id') if self.state == 'create' or self.state == 'update' or self.state == 'delete' or self.state == 'stop': return '/subscriptions' + '/' + sub_id \ + '/resourceGroups' + '/' + self.resource_group + '/providers' \ + '/Microsoft.RecoveryServices' + '/vaults' + '/' \ + self.recovery_vault_name \ + '/backupFabrics/Azure/protectionContainers/' \ + 'iaasvmcontainer;iaasvmcontainerv2;' + self.parse_resource_to_dict(self.resource_id)['resource_group']\ + ';' + self.parse_resource_to_dict(self.resource_id)['name'] + '/protectedItems/' \ + 'vm;iaasvmcontainerv2;' + self.parse_resource_to_dict(self.resource_id)['resource_group'] + ';' \ + self.parse_resource_to_dict(self.resource_id)['name'] if self.state == 'backup': return '/subscriptions' + '/' + sub_id \ + '/resourceGroups' + '/' + self.resource_group + '/providers' \ + '/Microsoft.RecoveryServices' + '/vaults' + '/' \ + self.recovery_vault_name \ + '/backupFabrics/Azure/protectionContainers/' \ + 'iaasvmcontainer;iaasvmcontainerv2;' + self.parse_resource_to_dict(self.resource_id)['resource_group'] \ + ';' + self.parse_resource_to_dict(self.resource_id)['name'] + '/protectedItems/' \ + 'vm;iaasvmcontainerv2;' + self.parse_resource_to_dict(self.resource_id)['resource_group'] + ';' \ + self.parse_resource_to_dict(self.resource_id)['name'] + '/backup' def get_body(self): if self.state == 'create' or self.state == 'update': return { 'properties': { 'protectedItemType': 'Microsoft.Compute/virtualMachines', 'sourceResourceId': self.resource_id, 'policyId': self.backup_policy_id } } elif self.state == 'backup': body = { "properties": { "objectType": "IaasVMBackupRequest" } } if self.recovery_point_expiry_time: body["properties"]["recoveryPointExpiryTimeInUTC"] = self.recovery_point_expiry_time return body elif self.state == 'stop': return { "properties": { "protectedItemType": "Microsoft.Compute/virtualMachines", "sourceResourceId": self.resource_id, "protectionState": "ProtectionStopped" } } else: return {} def exec_module(self, **kwargs): for key in list(self.module_arg_spec.keys()): if hasattr(self, key): setattr(self, key, kwargs[key]) elif kwargs[key] is not None: self.body[key] = kwargs[key] self.inflate_parameters(self.module_arg_spec, self.body, 0) self.query_parameters['api-version'] = self.get_api_version() self.url = self.get_url() self.body = self.get_body() old_response = None response = None self.mgmt_client = self.get_mgmt_svc_client(GenericRestClient, base_url=self._cloud_environment.endpoints.resource_manager) changed = False if self.state == 'create' or self.state == 'update': changed = True response = self.enable_update_protection_for_azure_vm() if self.state == 'delete': changed = True response = self.stop_protection_and_delete_data() if self.state == 'stop': changed = True response = self.stop_protection_but_retain_existing_data() if self.state == 'backup': changed = True response = self.trigger_on_demand_backup() self.results['response'] = response self.results['changed'] = changed return self.results def enable_update_protection_for_azure_vm(self): # self.log('Enabling/Updating protection for the Azure Virtual Machine {0}'.format(self.)) try: response = self.mgmt_client.query( self.url, 'PUT', self.query_parameters, self.header_parameters, self.body, self.status_code, 600, 30, ) except Exception as e: self.log('Error in enabling/updating protection for Azure VM.') self.fail( 'Error in creating/updating protection for Azure VM {0}'.format(str(e))) if hasattr(response, 'body'): response = json.loads(response.body()) elif hasattr(response, 'context'): response = response.context['deserialized_data'] else: self.fail("Create or Updating fail, no match message return, return info as {0}".format(response)) return response def stop_protection_but_retain_existing_data(self): # self.log('Stop protection and retain existing data{0}'.format(self.)) try: response = self.mgmt_client.query( self.url, 'PUT', self.query_parameters, self.header_parameters, self.body, self.status_code, 600, 30, ) except Exception as e: self.log('Error attempting to stop protection.') self.fail('Error in disabling the protection: {0}'.format(str(e))) if hasattr(response, 'body'): response = json.loads(response.body()) elif hasattr(response, 'context'): response = response.context['deserialized_data'] else: self.fail("Create or Updating fail, no match message return, return info as {0}".format(response)) return response def stop_protection_and_delete_data(self): # self.log('Stop protection and delete data{0}'.format(self.)) try: response = self.mgmt_client.query( self.url, 'DELETE', self.query_parameters, self.header_parameters, None, self.status_code, 600, 30, ) except Exception as e: self.log('Error attempting to delete backup.') self.fail('Error deleting the azure backup: {0}'.format(str(e))) if hasattr(response, 'body'): response = json.loads(response.body()) elif hasattr(response, 'context'): response = response.context['deserialized_data'] else: self.fail("Create or Updating fail, no match message return, return info as {0}".format(response)) return response def trigger_on_demand_backup(self): # self.log('Trigger an on-demand backup for a protected Azure VM{0}'.format(self.)) try: response = self.mgmt_client.query( self.url, 'POST', self.query_parameters, self.header_parameters, self.body, self.status_code, 600, 30, ) except Exception as e: self.log('Error attempting to backup azure vm.') self.fail( 'Error while taking on-demand backup: {0}'.format(str(e))) # The return value is None, which only triggers the backup. Backups also take some time to complete. response = dict(msg='The backup has been successfully triggered, please monitor the backup process on the Backup Jobs page') return response def main(): BackupAzureVM() if __name__ == '__main__': main()