From fff0ef9cfbb9cfed512dce0b92c4746bbba665ef Mon Sep 17 00:00:00 2001 From: Chris Houseknecht Date: Mon, 25 Apr 2016 09:28:52 -0400 Subject: [PATCH] Adding new module azure_rm_resourcegroup (#3490) * Adding new module azure_rm_resourcegroup * Fix poller error handling --- cloud/azure/azure_rm_resourcegroup.py | 285 ++++++++++++++++++++++++++ 1 file changed, 285 insertions(+) create mode 100644 cloud/azure/azure_rm_resourcegroup.py diff --git a/cloud/azure/azure_rm_resourcegroup.py b/cloud/azure/azure_rm_resourcegroup.py new file mode 100644 index 00000000000..775c40d2371 --- /dev/null +++ b/cloud/azure/azure_rm_resourcegroup.py @@ -0,0 +1,285 @@ +#!/usr/bin/python +# +# Copyright (c) 2016 Matt Davis, +# Chris Houseknecht, +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +DOCUMENTATION = ''' +--- +module: azure_rm_resourcegroup + +version_added: "2.1" + +short_description: Manage Azure resource groups. + +description: + - Create, update and delete a resource group. + +options: + force: + description: + - Remove a resource group and all associated resources. Use with state 'absent' to delete a resource + group that contains resources. + default: false + required: false + location: + description: + - Azure location for the resource group. Required when creating a new resource group. Cannot + be changed once resource group is created. + required: false + default: null + name: + description: + - Name of the resource group. + required: true + default: null + state: + description: + - Assert the state of the resource group. Use 'present' to create or update and + 'absent' to delete. When 'absent' a resource group containing resources will not be removed unless the + force option is used. + default: present + choices: + - absent + - present + required: false + tags: + description: + - "Dictionary of string:string pairs to assign as metadata to the object. Metadata tags on the object + will be updated with any provided values. To remove tags use the purge_tags option." + required: false + default: null + purge_tags: + description: + - Use to remove tags from an object. Any tags not found in the tags parameter will be removed from + the object's metadata. + default: false + required: false + +extends_documentation_fragment: + - azure + +author: + - "Chris Houseknecht (@chouseknecht)" + - "Matt Davis (@nitzmahone)" + +''' + +EXAMPLES = ''' + - name: Create a resource group + azure_rm_resourcegroup: + name: Testing + location: westus + tags: + testing: testing + delete: never + + - name: Delete a resource group + azure_rm_resourcegroup: + name: Testing + state: absent +''' +RETURN = ''' +changed: + description: Whether or not the object was changed. + returned: always + type: bool + sample: True +contains_resources: + description: Whether or not the resource group contains associated resources. + type: bool + sample: True +state: + description: Facts about the current state of the object. + returned: always + type: dict + sample: { + "id": "/subscriptions/XXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXX/resourceGroups/Testing", + "location": "westus", + "name": "Testing", + "provisioning_state": "Succeeded", + "tags": { + "delete": "on-exit", + "testing": "no" + } + } +''' + +from ansible.module_utils.basic import * +from ansible.module_utils.azure_rm_common import * + + +try: + from msrestazure.azure_exceptions import CloudError + from azure.common import AzureMissingResourceHttpError + from azure.mgmt.resource.resources.models import ResourceGroup +except ImportError: + pass + + +def resource_group_to_dict(rg): + return dict( + id=rg.id, + name=rg.name, + location=rg.location, + tags=rg.tags, + provisioning_state=rg.properties.provisioning_state + ) + + +class AzureRMResourceGroup(AzureRMModuleBase): + + def __init__(self): + self.module_arg_spec = dict( + name=dict(type='str', required=True), + state=dict(type='str', default='present', choices=['present', 'absent']), + location=dict(type='str'), + force=dict(type='bool', default=False) + ) + + self.name = None + self.state = None + self.location = None + self.tags = None + self.force = None + + self.results = dict( + changed=False, + contains_resources=False, + state=dict(), + ) + + super(AzureRMResourceGroup, self).__init__(self.module_arg_spec, + supports_check_mode=True, + supports_tags=True) + + def exec_module(self, **kwargs): + + for key in self.module_arg_spec.keys() + ['tags']: + setattr(self, key, kwargs[key]) + + results = dict() + changed = False + rg = None + contains_resources = False + + try: + self.log('Fetching resource group {0}'.format(self.name)) + rg = self.rm_client.resource_groups.get(self.name) + self.check_provisioning_state(rg, self.state) + contains_resources = self.resources_exist() + + results = resource_group_to_dict(rg) + if self.state == 'absent': + self.log("CHANGED: resource group {0} exists but requested state is 'absent'".format(self.name)) + changed = True + elif self.state == 'present': + update_tags, results['tags'] = self.update_tags(results['tags']) + if update_tags: + changed = True + + if self.location and self.location != results['location']: + self.fail("Resource group '{0}' already exists in location '{1}' and cannot be " + "moved.".format(self.name, results['location'])) + except CloudError: + self.log('Resource group {0} does not exist'.format(self.name)) + if self.state == 'present': + self.log("CHANGED: resource group {0} does not exist but requested state is " + "'present'".format(self.name)) + changed = True + + self.results['changed'] = changed + self.results['state'] = results + self.results['contains_resources'] = contains_resources + + if self.check_mode: + return self.results + + if changed: + if self.state == 'present': + if not rg: + # Create resource group + self.log("Creating resource group {0}".format(self.name)) + if not self.location: + self.fail("Parameter error: location is required when creating a resource " + "group.".format(self.name)) + if self.name_exists(): + self.fail("Error: a resource group with the name {0} already exists in your subscription." + .format(self.name)) + params = ResourceGroup( + location=self.location, + tags=self.tags + ) + else: + # Update resource group + params = ResourceGroup( + location=results['location'], + tags=results['tags'] + ) + self.results['state'] = self.create_or_update_resource_group(params) + elif self.state == 'absent': + if contains_resources and not self.force: + self.fail("Error removing resource group {0}. Resources exist within the group.".format(self.name)) + self.delete_resource_group() + + return self.results + + def create_or_update_resource_group(self, params): + try: + result = self.rm_client.resource_groups.create_or_update(self.name, params) + except Exception as exc: + self.fail("Error creating or updating resource group {0} - {1}".format(self.name, str(exc))) + return resource_group_to_dict(result) + + def delete_resource_group(self): + try: + poller = self.rm_client.resource_groups.delete(self.name) + self.get_poller_result(poller) + except Exception as exc: + self.fail("Error delete resource group {0} - {1}".format(self.name, str(exc))) + + # The delete operation doesn't return anything. + # If we got here, assume all is good + self.results['state']['status'] = 'Deleted' + return True + + def resources_exist(self): + found = False + try: + response = self.rm_client.resource_groups.list_resources(self.name) + except Exception as exc: + self.fail("Error checking for resource existence in {0} - {1}".format(self.name, str(exc))) + for item in response: + found = True + break + return found + + def name_exists(self): + try: + exists = self.rm_client.resource_groups.check_existence(self.name) + except Exception as exc: + self.fail("Error checking for existence of name {0} - {1}".format(self.name, str(exc))) + return exists + + +def main(): + AzureRMResourceGroup() + +if __name__ == '__main__': + main() +