1#!/usr/local/bin/python3.8
2# -*- coding: utf-8 -*-
3
4# Copyright: (c) 2018, Abhijeet Kasurde <akasurde@redhat.com>
5# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
6
7from __future__ import absolute_import, division, print_function
8__metaclass__ = type
9
10
11DOCUMENTATION = r'''
12---
13module: vmware_host_config_manager
14short_description: Manage advanced system settings of an ESXi host
15description:
16- This module can be used to manage advanced system settings of an ESXi host when ESXi hostname or Cluster name is given.
17author:
18- Abhijeet Kasurde (@Akasurde)
19notes:
20- Tested on vSphere 6.5
21requirements:
22- python >= 2.6
23- PyVmomi
24options:
25  cluster_name:
26    description:
27    - Name of the cluster.
28    - Settings are applied to every ESXi host in given cluster.
29    - If C(esxi_hostname) is not given, this parameter is required.
30    type: str
31  esxi_hostname:
32    description:
33    - ESXi hostname.
34    - Settings are applied to this ESXi host.
35    - If C(cluster_name) is not given, this parameter is required.
36    type: str
37  options:
38    description:
39    - A dictionary of advanced system settings.
40    - Invalid options will cause module to error.
41    - Note that the list of advanced options (with description and values) can be found by running `vim-cmd hostsvc/advopt/options`.
42    default: {}
43    type: dict
44extends_documentation_fragment:
45- community.vmware.vmware.documentation
46
47'''
48
49EXAMPLES = r'''
50- name: Manage Log level setting for all ESXi hosts in given Cluster
51  community.vmware.vmware_host_config_manager:
52    hostname: '{{ vcenter_hostname }}'
53    username: '{{ vcenter_username }}'
54    password: '{{ vcenter_password }}'
55    cluster_name: cluster_name
56    options:
57        'Config.HostAgent.log.level': 'info'
58  delegate_to: localhost
59
60- name: Manage Log level setting for an ESXi host
61  community.vmware.vmware_host_config_manager:
62    hostname: '{{ vcenter_hostname }}'
63    username: '{{ vcenter_username }}'
64    password: '{{ vcenter_password }}'
65    esxi_hostname: '{{ esxi_hostname }}'
66    options:
67        'Config.HostAgent.log.level': 'verbose'
68  delegate_to: localhost
69
70- name: Manage multiple settings for an ESXi host
71  community.vmware.vmware_host_config_manager:
72    hostname: '{{ vcenter_hostname }}'
73    username: '{{ vcenter_username }}'
74    password: '{{ vcenter_password }}'
75    esxi_hostname: '{{ esxi_hostname }}'
76    options:
77        'Config.HostAgent.log.level': 'verbose'
78        'Annotations.WelcomeMessage': 'Hello World'
79        'Config.HostAgent.plugins.solo.enableMob': false
80  delegate_to: localhost
81'''
82
83RETURN = r'''#
84'''
85
86try:
87    from pyVmomi import vim, vmodl, VmomiSupport
88except ImportError:
89    pass
90
91from ansible.module_utils.basic import AnsibleModule
92from ansible_collections.community.vmware.plugins.module_utils.vmware import vmware_argument_spec, PyVmomi, is_boolean, is_integer, is_truthy
93from ansible.module_utils._text import to_native
94from ansible.module_utils.six import integer_types, string_types
95
96
97class VmwareConfigManager(PyVmomi):
98    def __init__(self, module):
99        super(VmwareConfigManager, self).__init__(module)
100        cluster_name = self.params.get('cluster_name', None)
101        esxi_host_name = self.params.get('esxi_hostname', None)
102        self.options = self.params.get('options', dict())
103        self.hosts = self.get_all_host_objs(cluster_name=cluster_name, esxi_host_name=esxi_host_name)
104
105    def set_host_configuration_facts(self):
106        changed_list = []
107        message = ''
108        for host in self.hosts:
109            option_manager = host.configManager.advancedOption
110            host_facts = {}
111            for s_option in option_manager.supportedOption:
112                host_facts[s_option.key] = dict(option_type=s_option.optionType, value=None)
113
114            for option in option_manager.QueryOptions():
115                if option.key in host_facts:
116                    host_facts[option.key].update(
117                        value=option.value,
118                    )
119
120            change_option_list = []
121            for option_key, option_value in self.options.items():
122                if option_key in host_facts:
123                    # We handle all supported types here so we can give meaningful errors.
124                    option_type = host_facts[option_key]['option_type']
125                    if is_boolean(option_value) and isinstance(option_type, vim.option.BoolOption):
126                        option_value = is_truthy(option_value)
127                    elif (isinstance(option_value, integer_types) or is_integer(option_value))\
128                            and isinstance(option_type, vim.option.IntOption):
129                        option_value = VmomiSupport.vmodlTypes['int'](option_value)
130                    elif (isinstance(option_value, integer_types) or is_integer(option_value, 'long'))\
131                            and isinstance(option_type, vim.option.LongOption):
132                        option_value = VmomiSupport.vmodlTypes['long'](option_value)
133                    elif isinstance(option_value, float) and isinstance(option_type, vim.option.FloatOption):
134                        pass
135                    elif isinstance(option_value, string_types) and isinstance(option_type, (vim.option.StringOption, vim.option.ChoiceOption)):
136                        pass
137                    else:
138                        self.module.fail_json(msg="Provided value is of type %s."
139                                                  " Option %s expects: %s" % (type(option_value), option_key, type(option_type)))
140
141                    if option_value != host_facts[option_key]['value']:
142                        change_option_list.append(vim.option.OptionValue(key=option_key, value=option_value))
143                        changed_list.append(option_key)
144                else:  # Don't silently drop unknown options. This prevents typos from falling through the cracks.
145                    self.module.fail_json(msg="Unsupported option %s" % option_key)
146            if change_option_list:
147                if self.module.check_mode:
148                    changed_suffix = ' would be changed.'
149                else:
150                    changed_suffix = ' changed.'
151                if len(changed_list) > 2:
152                    message = ', '.join(changed_list[:-1]) + ', and ' + str(changed_list[-1])
153                elif len(changed_list) == 2:
154                    message = ' and '.join(changed_list)
155                elif len(changed_list) == 1:
156                    message = changed_list[0]
157                message += changed_suffix
158                if self.module.check_mode is False:
159                    try:
160                        option_manager.UpdateOptions(changedValue=change_option_list)
161                    except (vmodl.fault.SystemError, vmodl.fault.InvalidArgument) as e:
162                        self.module.fail_json(msg="Failed to update option/s as one or more OptionValue "
163                                                  "contains an invalid value: %s" % to_native(e.msg))
164                    except vim.fault.InvalidName as e:
165                        self.module.fail_json(msg="Failed to update option/s as one or more OptionValue "
166                                                  "objects refers to a non-existent option : %s" % to_native(e.msg))
167            else:
168                message = 'All settings are already configured.'
169
170        self.module.exit_json(changed=bool(changed_list), msg=message)
171
172
173def main():
174    argument_spec = vmware_argument_spec()
175    argument_spec.update(
176        cluster_name=dict(type='str', required=False),
177        esxi_hostname=dict(type='str', required=False),
178        options=dict(type='dict', default=dict(), required=False),
179    )
180
181    module = AnsibleModule(
182        argument_spec=argument_spec,
183        supports_check_mode=True,
184        required_one_of=[
185            ['cluster_name', 'esxi_hostname'],
186        ]
187    )
188
189    vmware_host_config = VmwareConfigManager(module)
190    vmware_host_config.set_host_configuration_facts()
191
192
193if __name__ == "__main__":
194    main()
195