diff --git a/lib/ansible/module_utils/vmware.py b/lib/ansible/module_utils/vmware.py index d9030b3b159..4af4599baa6 100644 --- a/lib/ansible/module_utils/vmware.py +++ b/lib/ansible/module_utils/vmware.py @@ -3,6 +3,9 @@ # Copyright: (c) 2018, Ansible Project # 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 + import atexit import os import ssl diff --git a/lib/ansible/module_utils/vmware_rest_client.py b/lib/ansible/module_utils/vmware_rest_client.py new file mode 100644 index 00000000000..0d85231cd92 --- /dev/null +++ b/lib/ansible/module_utils/vmware_rest_client.py @@ -0,0 +1,136 @@ +# -*- coding: utf-8 -*- +# Copyright: (c) 2018, Ansible Project +# Copyright: (c) 2018, Abhijeet Kasurde +# Simplified BSD License (see licenses/simplified_bsd.txt or https://opensource.org/licenses/BSD-2-Clause) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +try: + import requests + HAS_REQUESTS = True +except ImportError: + HAS_REQUESTS = False + +try: + from pyVim import connect + from pyVmomi import vim, vmodl + HAS_PYVMOMI = True +except ImportError: + HAS_PYVMOMI = False + +try: + from vmware.vapi.lib.connect import get_requests_connector + from vmware.vapi.security.session import create_session_security_context + from vmware.vapi.security.user_password import create_user_password_security_context + from com.vmware.cis_client import Session + from com.vmware.vapi.std_client import DynamicID + HAS_VCLOUD = True +except ImportError: + HAS_VCLOUD = False + +try: + from vmware.vapi.stdlib.client.factories import StubConfigurationFactory + HAS_VSPHERE = True +except ImportError: + HAS_VSPHERE = False + +from ansible.module_utils._text import to_native +from ansible.module_utils.basic import env_fallback + + +class VmwareRestClient(object): + def __init__(self, module): + """ + Constructor + + """ + self.module = module + self.params = module.params + self.check_required_library() + self.connect = self.connect_to_rest() + + def check_required_library(self): + """ + Function to check required libraries + + """ + if not HAS_REQUESTS: + self.module.fail_json(msg="Unable to find 'requests' Python library which is required." + " Please install using 'pip install requests'") + if not HAS_PYVMOMI: + self.module.fail_json(msg="PyVmomi Python module required. Install using 'pip install PyVmomi'") + if not HAS_VSPHERE: + self.module.fail_json(msg="Unable to find 'vSphere Automation SDK' Python library which is required." + " Please refer this URL for installation steps" + " - https://code.vmware.com/web/sdk/65/vsphere-automation-python") + if not HAS_VCLOUD: + self.module.fail_json(msg="Unable to find 'vCloud Suite SDK' Python library which is required." + " Please refer this URL for installation steps" + " - https://code.vmware.com/web/sdk/60/vcloudsuite-python") + + def connect_to_rest(self): + """ + Function to connect to server using username and password + + """ + session = requests.Session() + session.verify = self.params.get('validate_certs') + + username = self.params.get('username', None) + password = self.params.get('password', None) + + if not all([self.params.get('hostname', None), username, password]): + self.module.fail_json(msg="Missing one of the following : hostname, username, password." + " Please read the documentation for more information.") + + vcenter_url = "%(protocol)s://%(hostname)s/api" % self.params + + # Get request connector + connector = get_requests_connector(session=session, url=vcenter_url) + # Create standard Configuration + stub_config = StubConfigurationFactory.new_std_configuration(connector) + # Use username and password in the security context to authenticate + security_context = create_user_password_security_context(username, password) + # Login + stub_config.connector.set_security_context(security_context) + # Create the stub for the session service and login by creating a session. + session_svc = Session(stub_config) + session_id = None + try: + session_id = session_svc.create() + except OSError as os_err: + self.module.fail_json(msg="Failed to login to %s: %s" % (self.params['hostname'], + to_native(os_err))) + + if session_id is None: + self.module.fail_json(msg="Failed to create session using provided credentials." + " Please check hostname, username and password.") + # After successful authentication, store the session identifier in the security + # context of the stub and use that for all subsequent remote requests + session_security_context = create_session_security_context(session_id) + stub_config.connector.set_security_context(session_security_context) + + if stub_config is None: + self.module.fail_json(msg="Failed to login to %(hostname)s" % self.params) + return stub_config + + @staticmethod + def vmware_client_argument_spec(): + return dict( + hostname=dict(type='str', + fallback=(env_fallback, ['VMWARE_HOST'])), + username=dict(type='str', + fallback=(env_fallback, ['VMWARE_USER']), + aliases=['user', 'admin']), + password=dict(type='str', + fallback=(env_fallback, ['VMWARE_PASSWORD']), + aliases=['pass', 'pwd'], + no_log=True), + protocol=dict(type='str', + default='https', + choices=['https', 'http']), + validate_certs=dict(type='bool', + fallback=(env_fallback, ['VMWARE_VALIDATE_CERTS']), + default=True), + ) diff --git a/lib/ansible/modules/cloud/vmware/vmware_tag.py b/lib/ansible/modules/cloud/vmware/vmware_tag.py new file mode 100644 index 00000000000..5e46fb30adb --- /dev/null +++ b/lib/ansible/modules/cloud/vmware/vmware_tag.py @@ -0,0 +1,238 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright: (c) 2018, Ansible Project +# 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: vmware_tag +short_description: Manage VMware tags +description: +- This module can be used to create / delete / update VMware tags. +- Tag Feature is introduced in vSphere 6 version, so module not supported in eariler versions of vSphere. +- All variables and VMware object names are case sensitive. +version_added: '2.6' +author: +- Abhijeet Kasurde (@akasurde) +notes: +- Tested on vSphere 6.5 +requirements: +- python >= 2.6 +- PyVmomi +- vSphere Automation SDK +- vCloud Suite SDK +options: + tag_name: + description: + - The name of tag to manage. + required: True + tag_description: + description: + - The tag description. + - This is required only if C(state) is set to C(present). + - This parameter is ignored, when C(state) is set to C(absent). + - Process of updating tag only allows description change. + required: False + default: '' + category_id: + description: + - The unique ID generated by vCenter should be used to. + - User can get this unique ID from facts module. + required: False + state: + description: + - The state of tag. + - If set to C(present) and tag does not exists, then tag is created. + - If set to C(present) and tag exists, then tag is updated. + - If set to C(absent) and tag exists, then tag is deleted. + - If set to C(absent) and tag does not exists, no action is taken. + required: False + default: 'present' + choices: [ 'present', 'absent' ] +extends_documentation_fragment: vmware_rest_client.documentation +''' + +EXAMPLES = r''' +- name: Create a tag + vmware_tag: + hostname: 10.65.223.91 + username: administrator@vsphere.local + password: Esxi@123$ + validate_certs: False + category_id: 'urn:vmomi:InventoryServiceCategory:e785088d-6981-4b1c-9fb8-1100c3e1f742:GLOBAL' + tag_name: Sample_Tag_0002 + tag_description: Sample Description + state: present + +- name: Update tag description + vmware_tag: + hostname: 10.65.223.91 + username: administrator@vsphere.local + password: Esxi@123$ + validate_certs: False + tag_name: Sample_Tag_0002 + tag_description: Some fancy description + state: present + +- name: Delete tag + vmware_tag: + hostname: 10.65.223.91 + username: administrator@vsphere.local + password: Esxi@123$ + validate_certs: False + tag_name: Sample_Tag_0002 + state: absent +''' + +RETURN = r''' +results: + description: dictionary of tag metadata + returned: on success + type: dict + sample: { + "msg": "Tag 'Sample_Tag_0002' created.", + "tag_id": "urn:vmomi:InventoryServiceTag:bff91819-f529-43c9-80ca-1c9dfda09441:GLOBAL" + } +''' + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.vmware_rest_client import VmwareRestClient +try: + from com.vmware.cis.tagging_client import Tag +except ImportError: + pass + + +class VmwareTag(VmwareRestClient): + def __init__(self, module): + super(VmwareTag, self).__init__(module) + self.tag_service = Tag(self.connect) + self.global_tags = dict() + self.tag_name = self.params.get('tag_name') + self.get_all_tags() + + def ensure_state(self): + """ + Function to manage internal states of tags + + """ + desired_state = self.params.get('state') + states = { + 'present': { + 'present': self.state_update_tag, + 'absent': self.state_create_tag, + }, + 'absent': { + 'present': self.state_delete_tag, + 'absent': self.state_unchanged, + } + } + states[desired_state][self.check_tag_status()]() + + def state_create_tag(self): + """ + Function to create tag + + """ + tag_spec = self.tag_service.CreateSpec() + tag_spec.name = self.tag_name + tag_spec.description = self.params.get('tag_description') + category_id = self.params.get('category_id', None) + if category_id is None: + self.module.fail_json(msg="'category_id' is required parameter while creating tag.") + tag_spec.category_id = category_id + tag_id = self.tag_service.create(tag_spec) + if tag_id: + self.module.exit_json(changed=True, + results=dict(msg="Tag '%s' created." % tag_spec.name, + tag_id=tag_id)) + self.module.exit_json(changed=False, + results=dict(msg="No tag created", tag_id='')) + + def state_unchanged(self): + """ + Function to return unchanged state + + """ + self.module.exit_json(changed=False) + + def state_update_tag(self): + """ + Function to update tag + + """ + changed = False + results = dict(msg="Tag %s is unchanged." % self.tag_name, + tag_id=self.global_tags[self.tag_name]['tag_id']) + tag_update_spec = self.tag_service.UpdateSpec() + tag_desc = self.global_tags[self.tag_name]['tag_description'] + desired_tag_desc = self.params.get('tag_description') + if tag_desc != desired_tag_desc: + tag_update_spec.setDescription = desired_tag_desc + results['msg'] = 'Tag %s updated.' % self.tag_name + changed = True + + self.module.exit_json(changed=changed, results=results) + + def state_delete_tag(self): + """ + Function to delete tag + + """ + tag_id = self.global_tags[self.tag_name]['tag_id'] + self.tag_service.delete(tag_id=tag_id) + self.module.exit_json(changed=True, + results=dict(msg="Tag '%s' deleted." % self.tag_name, + tag_id=tag_id)) + + def check_tag_status(self): + """ + Function to check if tag exists or not + Returns: 'present' if tag found, else 'absent' + + """ + if self.tag_name in self.global_tags: + return 'present' + else: + return 'absent' + + def get_all_tags(self): + """ + Function to retrieve all tag information + + """ + for tag in self.tag_service.list(): + tag_obj = self.tag_service.get(tag) + self.global_tags[tag_obj.name] = dict(tag_description=tag_obj.description, + tag_used_by=tag_obj.used_by, + tag_category_id=tag_obj.category_id, + tag_id=tag_obj.id + ) + + +def main(): + argument_spec = VmwareRestClient.vmware_client_argument_spec() + argument_spec.update( + tag_name=dict(type='str', required=True), + tag_description=dict(type='str', default='', required=False), + category_id=dict(type='str', required=False), + state=dict(type='str', choices=['present', 'absent'], default='present', required=False), + ) + module = AnsibleModule(argument_spec=argument_spec) + + vmware_tag = VmwareTag(module) + vmware_tag.ensure_state() + + +if __name__ == '__main__': + main() diff --git a/lib/ansible/utils/module_docs_fragments/vmware_rest_client.py b/lib/ansible/utils/module_docs_fragments/vmware_rest_client.py new file mode 100644 index 00000000000..5d1ec27fed3 --- /dev/null +++ b/lib/ansible/utils/module_docs_fragments/vmware_rest_client.py @@ -0,0 +1,39 @@ +# Copyright: (c) 2018, Ansible Project +# Copyright: (c) 2018, Abhijeet Kasurde +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + + +class ModuleDocFragment(object): + # Parameters for VMware REST Client based modules + DOCUMENTATION = ''' +options: + hostname: + description: + - The hostname or IP address of the vSphere vCenter server. + - If the value is not specified in the task, the value of environment variable C(VMWARE_HOST) will be used instead. + required: False + username: + description: + - The username of the vSphere vCenter server. + - If the value is not specified in the task, the value of environment variable C(VMWARE_USER) will be used instead. + aliases: ['user', 'admin'] + password: + description: + - The password of the vSphere vCenter server. + - If the value is not specified in the task, the value of environment variable C(VMWARE_PASSWORD) will be used instead. + required: False + aliases: ['pass', 'pwd'] + validate_certs: + description: + - Allows connection when SSL certificates are not valid. Set to C(false) when certificates are not trusted. + - If the value is not specified in the task, the value of environment variable C(VMWARE_VALIDATE_CERTS) will be used instead. + default: True + type: bool + required: False + protocol: + description: + - The connection to protocol. + choices: ['https', 'http'] + default: 'https' + required: False +'''