Skip to content

Commit 2bc2625

Browse files
committed
feat(vmware_custom_attribute): Enhance custom attribute management – extended object types, refactored code
1 parent 9011b8e commit 2bc2625

File tree

1 file changed

+194
-94
lines changed

1 file changed

+194
-94
lines changed
+194-94
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,39 @@
11
#!/usr/bin/python
22
# -*- coding: utf-8 -*-
3-
43
# Copyright: (c) 2022, Ansible Project
54
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
65
# SPDX-License-Identifier: GPL-3.0-or-later
6+
"""
7+
Module for managing VMware custom attribute definitions.
8+
"""
9+
10+
from types import ModuleType
11+
from typing import Any, Dict, Optional
712

8-
from __future__ import absolute_import, division, print_function
9-
__metaclass__ = type
13+
from ansible.module_utils.basic import AnsibleModule
14+
from ansible_collections.community.vmware.plugins.module_utils.vmware import (
15+
PyVmomi,
16+
vmware_argument_spec,
17+
)
1018

19+
try:
20+
from pyVmomi import vim as _vim # type: ignore
21+
except ImportError:
22+
_vim = None # type: ignore
1123

12-
DOCUMENTATION = r'''
24+
vim: Optional[ModuleType] = _vim
25+
26+
DOCUMENTATION = r"""
1327
---
1428
module: vmware_custom_attribute
15-
version_added: '3.2.0'
16-
short_description: Manage custom attributes definitions
29+
version_added: "3.3.0"
30+
short_description: Manage custom attribute definitions for vSphere objects.
1731
description:
18-
- This module can be used to add and remove custom attributes definitions for various vSphere objects.
32+
- This module adds or removes custom attribute definitions for various vSphere objects.
33+
- It supports all object types provided by VMware (e.g. Cluster, Datacenter, VirtualMachine, etc.).
1934
author:
2035
- Mario Lenz (@mariolenz)
36+
- Simon Baerlocher (@sbaerlocher)
2137
options:
2238
custom_attribute:
2339
description:
@@ -27,6 +43,8 @@
2743
object_type:
2844
description:
2945
- Type of the object the custom attribute is associated with.
46+
- All supported types are listed here.
47+
required: true
3048
type: str
3149
choices:
3250
- Cluster
@@ -39,7 +57,8 @@
3957
- HostSystem
4058
- ResourcePool
4159
- VirtualMachine
42-
required: true
60+
- Network
61+
- VirtualApp
4362
state:
4463
description:
4564
- Manage definition of custom attributes.
@@ -51,11 +70,10 @@
5170
choices: ['present', 'absent']
5271
type: str
5372
extends_documentation_fragment:
54-
- community.vmware.vmware.documentation
55-
56-
'''
73+
- community.vmware.vmware.documentation
74+
"""
5775

58-
EXAMPLES = r'''
76+
EXAMPLES = r"""
5977
- name: Add VM Custom Attribute Definition
6078
community.vmware.vmware_custom_attribute:
6179
hostname: "{{ vcenter_hostname }}"
@@ -65,7 +83,7 @@
6583
object_type: VirtualMachine
6684
custom_attribute: custom_attr_def_1
6785
delegate_to: localhost
68-
register: defs
86+
register: definitions
6987
7088
- name: Remove VM Custom Attribute Definition
7189
community.vmware.vmware_custom_attribute:
@@ -76,100 +94,182 @@
7694
object_type: VirtualMachine
7795
custom_attribute: custom_attr_def_1
7896
delegate_to: localhost
79-
register: defs
80-
'''
97+
register: definitions
8198
82-
RETURN = r'''
83-
'''
99+
- name: Add Network Custom Attribute Definition
100+
community.vmware.vmware_custom_attribute:
101+
hostname: "{{ vcenter_hostname }}"
102+
username: "{{ vcenter_username }}"
103+
password: "{{ vcenter_password }}"
104+
state: present
105+
object_type: Network
106+
custom_attribute: custom_attr_network
107+
delegate_to: localhost
108+
register: definitions
109+
"""
84110

85-
from ansible.module_utils.basic import AnsibleModule
86-
from ansible_collections.community.vmware.plugins.module_utils.vmware import PyVmomi, vmware_argument_spec
111+
RETURN = r"""
112+
changed:
113+
description: Indicates if any change was made.
114+
type: bool
115+
failed:
116+
description: Indicates if the operation failed.
117+
type: bool
118+
"""
87119

88-
try:
89-
from pyVmomi import vim
90-
except ImportError:
91-
pass
92120

121+
def get_object_type_mapping() -> Dict[str, Any]:
122+
"""Returns a mapping from object type names to the corresponding pyVmomi classes."""
123+
return {
124+
"Cluster": vim.ClusterComputeResource if vim else None,
125+
"Datacenter": vim.Datacenter if vim else None,
126+
"Datastore": vim.Datastore if vim else None,
127+
"DistributedVirtualPortgroup": (
128+
vim.dvs.DistributedVirtualPortgroup if vim else None
129+
),
130+
"DistributedVirtualSwitch": vim.DistributedVirtualSwitch if vim else None,
131+
"Folder": vim.Folder if vim else None,
132+
"Global": None,
133+
"HostSystem": vim.HostSystem if vim else None,
134+
"ResourcePool": vim.ResourcePool if vim else None,
135+
"VirtualMachine": vim.VirtualMachine if vim else None,
136+
"Network": vim.Network if vim else None,
137+
"VirtualApp": getattr(vim, "VirtualApp", None) if vim else None,
138+
}
93139

94-
class CustomAttribute(PyVmomi):
95-
def __init__(self, module):
96-
super(CustomAttribute, self).__init__(module)
140+
141+
class CustomAttributeManager(PyVmomi):
142+
"""Class responsible for managing custom attribute definitions."""
143+
144+
def __init__(self, module: AnsibleModule) -> None:
145+
super().__init__(module)
146+
self.module = module
147+
148+
if not isinstance(module.params, dict):
149+
self.module.fail_json(msg="module.params is not a dict")
150+
self.parameters: Dict[str, Any] = module.params
151+
152+
custom_attribute_value = self.parameters.get("custom_attribute", "")
153+
if (
154+
not isinstance(custom_attribute_value, str)
155+
or not custom_attribute_value.strip()
156+
):
157+
self.module.fail_json(msg="'custom_attribute' must be a non-empty string")
158+
159+
if vim is None:
160+
self.module.fail_json(msg="pyVmomi is required for this module")
97161

98162
if not self.is_vcenter():
99-
self.module.fail_json(msg="You have to connect to a vCenter server!")
100-
101-
object_types_map = {
102-
'Cluster': vim.ClusterComputeResource,
103-
'Datacenter': vim.Datacenter,
104-
'Datastore': vim.Datastore,
105-
'DistributedVirtualPortgroup': vim.DistributedVirtualPortgroup,
106-
'DistributedVirtualSwitch': vim.DistributedVirtualSwitch,
107-
'Folder': vim.Folder,
108-
'Global': None,
109-
'HostSystem': vim.HostSystem,
110-
'ResourcePool': vim.ResourcePool,
111-
'VirtualMachine': vim.VirtualMachine
112-
}
113-
114-
self.object_type = object_types_map[self.params['object_type']]
115-
116-
def remove_custom_def(self, field):
117-
changed = False
118-
for x in self.custom_field_mgr:
119-
if x.name == field and x.managedObjectType == self.object_type:
120-
changed = True
121-
if not self.module.check_mode:
122-
self.content.customFieldsManager.RemoveCustomFieldDef(key=x.key)
123-
break
124-
return {'changed': changed, 'failed': False}
125-
126-
def add_custom_def(self, field):
127-
changed = False
128-
found = False
129-
for x in self.custom_field_mgr:
130-
if x.name == field and x.managedObjectType == self.object_type:
131-
found = True
132-
break
133-
134-
if not found:
135-
changed = True
163+
self.module.fail_json(msg="A connection to a vCenter server is required!")
164+
165+
object_type_value = self.parameters.get("object_type", "")
166+
if not isinstance(object_type_value, str) or not object_type_value.strip():
167+
self.module.fail_json(msg="'object_type' must be a non-empty string")
168+
169+
object_type_mapping = get_object_type_mapping()
170+
self.object_type = object_type_mapping.get(object_type_value)
171+
if self.object_type is None and object_type_value != "Global":
172+
self.module.fail_json(msg=f"Unsupported object type: {object_type_value}")
173+
174+
try:
175+
self.custom_field_definitions = self.content.customFieldsManager.field
176+
except AttributeError:
177+
self.module.fail_json(
178+
msg="Failed to access customFieldsManager in vCenter content"
179+
)
180+
181+
def find_custom_attribute_definition(
182+
self, custom_attribute_name: str
183+
) -> Optional[Any]:
184+
"""Searches for a custom attribute definition and returns it if found."""
185+
for custom_field_definition in self.custom_field_definitions:
186+
if (
187+
custom_field_definition.name == custom_attribute_name
188+
and custom_field_definition.managedObjectType == self.object_type
189+
):
190+
return custom_field_definition
191+
return None
192+
193+
def remove_custom_definition(self, custom_attribute_name: str) -> Dict[str, Any]:
194+
"""Removes the custom attribute definition if it exists."""
195+
state_changed = False
196+
custom_field_definition = self.find_custom_attribute_definition(
197+
custom_attribute_name
198+
)
199+
if custom_field_definition:
200+
state_changed = True
201+
if not self.module.check_mode:
202+
self.content.customFieldsManager.RemoveCustomFieldDef(
203+
key=custom_field_definition.key
204+
)
205+
return {"changed": state_changed, "failed": False}
206+
207+
def add_custom_definition(self, custom_attribute_name: str) -> Dict[str, Any]:
208+
"""Adds the custom attribute definition if it does not exist."""
209+
state_changed = False
210+
if not self.find_custom_attribute_definition(custom_attribute_name):
211+
state_changed = True
136212
if not self.module.check_mode:
137-
self.content.customFieldsManager.AddFieldDefinition(name=field, moType=self.object_type)
138-
return {'changed': changed, 'failed': False}
139-
140-
141-
def main():
142-
argument_spec = vmware_argument_spec()
143-
argument_spec.update(
144-
custom_attribute=dict(type='str', no_log=False, required=True),
145-
object_type=dict(type='str', required=True, choices=[
146-
'Cluster',
147-
'Datacenter',
148-
'Datastore',
149-
'DistributedVirtualPortgroup',
150-
'DistributedVirtualSwitch',
151-
'Folder',
152-
'Global',
153-
'HostSystem',
154-
'ResourcePool',
155-
'VirtualMachine'
156-
]),
157-
state=dict(type='str', default='present', choices=['absent', 'present']),
213+
self.content.customFieldsManager.AddFieldDefinition(
214+
name=custom_attribute_name, moType=self.object_type
215+
)
216+
return {"changed": state_changed, "failed": False}
217+
218+
219+
def manage_custom_attribute_definition(module: AnsibleModule) -> None:
220+
"""Determines whether to add or remove the custom attribute definition based on the 'state' parameter."""
221+
if not isinstance(module.params, dict):
222+
module.fail_json(msg="module.params is not a dict")
223+
parameters: Dict[str, Any] = module.params
224+
custom_attribute_name = parameters["custom_attribute"]
225+
desired_state = parameters["state"]
226+
custom_attribute_manager = CustomAttributeManager(module)
227+
if desired_state == "present":
228+
result = custom_attribute_manager.add_custom_definition(custom_attribute_name)
229+
else:
230+
result = custom_attribute_manager.remove_custom_definition(
231+
custom_attribute_name
232+
)
233+
module.exit_json(**result)
234+
235+
236+
def main() -> None:
237+
"""Main entry point for the module."""
238+
argument_specification = vmware_argument_spec()
239+
argument_specification.update(
240+
custom_attribute={"type": "str", "no_log": False, "required": True},
241+
object_type={
242+
"type": "str",
243+
"required": True,
244+
"choices": [
245+
"Cluster",
246+
"Datacenter",
247+
"Datastore",
248+
"DistributedVirtualPortgroup",
249+
"DistributedVirtualSwitch",
250+
"Folder",
251+
"Global",
252+
"HostSystem",
253+
"ResourcePool",
254+
"VirtualMachine",
255+
"Network",
256+
"VirtualApp",
257+
],
258+
},
259+
state={"type": "str", "default": "present", "choices": ["absent", "present"]},
158260
)
159261
module = AnsibleModule(
160-
argument_spec=argument_spec,
262+
argument_spec=argument_specification,
161263
supports_check_mode=True,
162264
)
163265

164-
pyv = CustomAttribute(module)
165-
results = dict(changed=False, custom_attribute_defs=list())
166-
if module.params['state'] == "present":
167-
results = pyv.add_custom_def(module.params['custom_attribute'])
168-
elif module.params['state'] == "absent":
169-
results = pyv.remove_custom_def(module.params['custom_attribute'])
170-
171-
module.exit_json(**results)
266+
try:
267+
manage_custom_attribute_definition(module)
268+
except ValueError as error:
269+
module.fail_json(msg=f"ValueError: {error}")
270+
except KeyError as error:
271+
module.fail_json(msg=f"KeyError: {error}")
172272

173273

174-
if __name__ == '__main__':
274+
if __name__ == "__main__":
175275
main()

0 commit comments

Comments
 (0)