diff --git a/lib/ansible/modules/cloud/vmware/vcenter_folder.py b/lib/ansible/modules/cloud/vmware/vcenter_folder.py new file mode 100644 index 0000000000..3090b100f1 --- /dev/null +++ b/lib/ansible/modules/cloud/vmware/vcenter_folder.py @@ -0,0 +1,252 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright: (c) 2018, Abhijeet Kasurde +# 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 + + +ANSIBLE_METADATA = { + 'metadata_version': '1.1', + 'status': ['preview'], + 'supported_by': 'community' +} + +DOCUMENTATION = r''' +--- +module: vcenter_folder +short_description: Manage folders on given datacenter +description: +- This module can be used to create, delete, move and rename folder on then given datacenter. +version_added: '2.5' +author: +- Abhijeet Kasurde (@akasurde) +notes: +- Tested on vSphere 6.5 +requirements: +- python >= 2.6 +- PyVmomi +options: + datacenter: + description: + - Name of the datacenter. + required: True + folder_name: + description: + - Name of folder to be managed. + - This is case sensitive parameter. + - Folder name should be under 80 characters. This is a VMware restriction. + required: True + parent_folder: + description: + - Name of the parent folder under which new folder needs to be created. + - This is case sensitive parameter. + - Please specify unique folder name as there is no way to detect duplicate names. + - "If user wants to create a folder under '/DC0/vm/vm_folder', this value will be 'vm_folder'." + required: False + folder_type: + description: + - This is type of folder. + - "If set to C(vm), then 'VM and Template Folder' is created under datacenter." + - "If set to C(host), then 'Host and Cluster Folder' is created under datacenter." + - "If set to C(datastore), then 'Storage Folder' is created under datacenter." + - "If set to C(network), then 'Network Folder' is created under datacenter." + - This parameter is required, if C(state) is set to C(present) and parent_folder is absent. + - This option is ignored, if C(parent_folder) is set. + default: vm + required: False + state: + description: + - State of folder. + - If set to C(present) without parent folder parameter, then folder with C(folder_type) is created. + - If set to C(present) with parent folder parameter, then folder in created under parent folder. C(folder_type) is ignored. + - If set to C(absent), then folder is unregistered and destroyed. + default: present + choices: [ present, absent ] +extends_documentation_fragment: vmware.documentation +''' + +EXAMPLES = r''' +- name: Create a VM folder on given datacenter + vcenter_folder: + hostname: '{{ vcenter_hostname }}' + username: '{{ vcenter_username }}' + password: '{{ vcenter_password }}' + datacenter: datacenter_name + folder_name: sample_vm_folder + folder_type: vm + state: present + register: vm_folder_creation_result + +- name: Create a datastore folder on given datacenter + vcenter_folder: + hostname: '{{ vcenter_hostname }}' + username: '{{ vcenter_username }}' + password: '{{ vcenter_password }}' + datacenter: datacenter_name + folder_name: sample_datastore_folder + folder_type: datastore + state: present + register: datastore_folder_creation_result + +- name: Create a sub folder under VM folder on given datacenter + vcenter_folder: + hostname: '{{ vcenter_hostname }}' + username: '{{ vcenter_username }}' + password: '{{ vcenter_password }}' + datacenter: datacenter_name + folder_name: sample_sub_folder + parent_folder: vm_folder + state: present + register: sub_folder_creation_result + +- name: Delete a VM folder on given datacenter + vcenter_folder: + hostname: '{{ vcenter_hostname }}' + username: '{{ vcenter_username }}' + password: '{{ vcenter_password }}' + datacenter: datacenter_name + folder_name: sample_vm_folder + folder_type: vm + state: absent + register: vm_folder_deletion_result + +''' + +RETURN = r''' +result: + description: + - string stating about result + returned: success + type: string + sample: "Folder 'sub_network_folder' of type 'vm' created under vm_folder successfully." +''' + +try: + from pyVmomi import vim, vmodl +except ImportError as e: + pass + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.vmware import vmware_argument_spec, PyVmomi, find_datacenter_by_name, wait_for_task, get_all_objs +from ansible.module_utils._text import to_native + + +class VmwareFolderManager(PyVmomi): + def __init__(self, module): + super(VmwareFolderManager, self).__init__(module) + datacenter_name = self.params.get('datacenter', None) + self.datacenter_obj = find_datacenter_by_name(self.content, datacenter_name=datacenter_name) + if self.datacenter_obj is None: + self.module.fail_json(msg="Failed to find datacenter %s" % datacenter_name) + + def ensure(self): + """ + Function to manage internal state management + Returns: + + """ + state = self.module.params.get('state') + folder_type = self.module.params.get('folder_type') + folder_name = self.module.params.get('folder_name') + parent_folder = self.module.params.get('parent_folder', None) + results = dict(changed=False, result=dict()) + if state == 'present': + # Create a new folder + try: + if parent_folder: + folder = self.get_folder_by_name(folder_name=parent_folder) + if folder: + folder.CreateFolder(folder_name) + results['changed'] = True + results['result'] = "Folder '%s' of type '%s' created under %s" \ + " successfully." % (folder_name, folder_type, parent_folder) + else: + self.module.fail_json(msg="Failed to find the parent folder %s" + " for folder %s" % (parent_folder, folder_name)) + else: + datacenter_folder_type = { + 'vm': self.datacenter_obj.vmFolder, + 'host': self.datacenter_obj.hostFolder, + 'datastore': self.datacenter_obj.datastoreFolder, + 'network': self.datacenter_obj.networkFolder, + } + datacenter_folder_type[folder_type].CreateFolder(folder_name) + results['changed'] = True + results['result'] = "Folder '%s' of type '%s' created successfully" % (folder_name, folder_type) + except vim.fault.DuplicateName as duplicate_name: + # To be consistent with the other vmware modules, We decided to accept this error + # and the playbook should simply carry on with other tasks. + # User will have to take care of this exception + # https://github.com/ansible/ansible/issues/35388#issuecomment-362283078 + results['changed'] = False + results['result'] = "Failed to create folder as another object has same name" \ + " in the same target folder : %s" % to_native(duplicate_name.msg) + except vim.fault.InvalidName as invalid_name: + self.module.fail_json(msg="Failed to create folder as folder name is not a valid " + "entity name : %s" % to_native(invalid_name.msg)) + except Exception as general_exc: + self.module.fail_json(msg="Failed to create folder due to generic" + " exception : %s " % to_native(general_exc)) + self.module.exit_json(**results) + elif state == 'absent': + folder_obj = self.get_folder_by_name(folder_name=folder_name) + if folder_obj: + try: + task = folder_obj.UnregisterAndDestroy() + results['changed'], results['result'] = wait_for_task(task=task) + except vim.fault.ConcurrentAccess as concurrent_access: + self.module.fail_json(msg="Failed to remove folder as another client" + " modified folder before this operation : %s" % to_native(concurrent_access.msg)) + except vim.fault.InvalidState as invalid_state: + self.module.fail_json(msg="Failed to remove folder as folder is in" + " invalid state" % to_native(invalid_state.msg)) + except Exception as e: + self.module.fail_json(msg="Failed to remove folder due to generic" + " exception %s " % to_native(e)) + self.module.exit_json(**results) + + def get_folder_by_name(self, folder_name): + """ + Function to get managed object of folder by name + Returns: Managed object of folder by name + + """ + folder_objs = get_all_objs(self.content, [vim.Folder]) + for folder in folder_objs: + if folder.name == folder_name: + return folder + + return None + + +def main(): + argument_spec = vmware_argument_spec() + argument_spec.update( + datacenter=dict(type='str', required=True), + folder_name=dict(type='str', required=True), + parent_folder=dict(type='str', required=False), + state=dict(type='str', + choices=['present', 'absent'], + default='present'), + folder_type=dict(type='str', + default='vm', + choices=['datastore', 'host', 'network', 'vm'], + required=False), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=False, + ) + + if len(module.params.get('folder_name')) > 79: + module.fail_json(msg="Failed to manage folder as folder_name can only contain 80 characters.") + + vcenter_folder_mgr = VmwareFolderManager(module) + vcenter_folder_mgr.ensure() + + +if __name__ == "__main__": + main() diff --git a/test/integration/targets/vcenter_folder/aliases b/test/integration/targets/vcenter_folder/aliases new file mode 100644 index 0000000000..dcb429935f --- /dev/null +++ b/test/integration/targets/vcenter_folder/aliases @@ -0,0 +1,3 @@ +posix/ci/cloud/group4/vcenter +cloud/vcenter +destructive diff --git a/test/integration/targets/vcenter_folder/tasks/main.yml b/test/integration/targets/vcenter_folder/tasks/main.yml new file mode 100644 index 0000000000..bcb446f24e --- /dev/null +++ b/test/integration/targets/vcenter_folder/tasks/main.yml @@ -0,0 +1,144 @@ +# Test code for the vcenter_folder module. +# Copyright: (c) 2018, Abhijeet Kasurde +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +- name: make sure pyvmomi is installed + pip: + name: pyvmomi + state: latest + when: "{{ ansible_user_id == 'root' }}" + +- name: store the vcenter container ip + set_fact: + vcsim: "{{ lookup('env', 'vcenter_host') }}" + +- debug: var=vcsim + +- name: Wait for Flask controller to come up online + wait_for: + host: "{{ vcsim }}" + port: 5000 + state: started + +- name: kill vcsim + uri: + url: http://{{ vcsim }}:5000/killall + +- name: start vcsim + uri: + url: http://{{ vcsim }}:5000/spawn?cluster=2 + register: vcsim_instance + +- debug: + var: vcsim_instance + +- name: Wait for vcsim server to come up online + wait_for: + host: "{{ vcsim }}" + port: 443 + state: started + +- name: get datacenter + uri: + url: http://{{ vcsim }}:5000/govc_find?filter=DC + register: datacenters + +- name: get a datacenter + set_fact: + dc1: "{{ datacenters.json[0] | basename }}" + +- name: Create all types of folder + vcenter_folder: + hostname: "{{ vcsim }}" + username: "{{ vcsim_instance.json.username }}" + password: "{{ vcsim_instance.json.password }}" + validate_certs: no + datacenter: "{{ dc1 }}" + folder_name: "{{ item }}_folder" + folder_type: "{{ item }}" + state: present + register: all_folder_results + with_items: + - vm + - host + - datastore + - network + +- debug: msg="{{ all_folder_results }}" + +- name: ensure everything for {{ dc1 }} + assert: + that: + - all_folder_results.changed + +- name: Create all types of sub folder + vcenter_folder: + hostname: "{{ vcsim }}" + username: "{{ vcsim_instance.json.username }}" + password: "{{ vcsim_instance.json.password }}" + validate_certs: no + datacenter: "{{ dc1 }}" + folder_name: "sub_{{ item }}_folder" + parent_folder: 'vm_folder' + state: present + register: all_folder_results + with_items: + - vm + - host + - datastore + - network + +- debug: msg="{{ all_folder_results }}" + +- name: ensure everything for {{ dc1 }} + assert: + that: + - all_folder_results.changed + +#- name: Delete all types of folder +# vcenter_folder: +# hostname: "{{ vcsim }}" +# username: "{{ vcsim_instance.json.username }}" +# password: "{{ vcsim_instance.json.password }}" +# validate_certs: no +# datacenter: "{{ dc1 }}" +# folder_name: "{{ item }}_folder" +# folder_type: "{{ item }}" +# state: absent +# register: all_folder_results +# with_items: +# - vm +# - host +# - datastore +# - network + +#- debug: msg="{{ all_folder_results }}" + +#- name: ensure everything for {{ dc1 }} +# assert: +# that: +# - all_folder_results.changed + +#- name: Delete all types of folder again +# vcenter_folder: +# hostname: "{{ vcsim }}" +# username: "{{ vcsim_instance.json.username }}" +# password: "{{ vcsim_instance.json.password }}" +# validate_certs: no +# datacenter: "{{ dc1 }}" +# folder_name: "{{ item }}_folder" +# folder_type: "{{ item }}" +# state: absent +# register: all_folder_results +# with_items: +# - vm +# - host +# - datastore +# - network + +#- debug: msg="{{ all_folder_results }}" + +#- name: ensure everything for {{ dc1 }} +# assert: +# that: +# - not all_folder_results.changed