Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/add vm inventory #96

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions changelogs/fragments/96-add-vms-inventory-plugin.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
---
minor_changes:
- vms - added vms inventory plugin. consolidated shared docs/code with esxi hosts inventory plugin
72 changes: 72 additions & 0 deletions plugins/doc_fragments/plugin_base_options.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,76 @@ class ModuleDocFragment(object):
proxy_port:
env:
- name: VMWARE_PROXY_PORT
gather_tags:
description:
- If true, gather any tags attached to the associated VMs
- Requires 'vSphere Automation SDK' library to be installed on the Ansible controller machine.
default: false
type: bool
hostnames:
description:
- A list of templates evaluated in order to compose inventory_hostname.
- Each value in the list should be a jinja template. You can see the examples section for more details.
- Templates that result in an empty string or None value are ignored and the next template is evaluated.
- You can use hostvars such as properties specified in O(properties) as variables in the template.
type: list
elements: string
default: ['name']
properties:
description:
- Specify a list of VMware schema properties associated with the host to collect and return as hostvars.
- Each value in the list can be a path to a specific property in a VMware object or a path to a collection of properties.
- Please make sure that if you use a property in another parameter that it is included in this option.
- Some properties are always returned, such as name, customValue, and summary.runtime.powerState
- Use V(all) to return all properties available for the VMware object.
type: list
elements: string
default: [
'name', 'customValue', 'summary.runtime.powerState'
]
flatten_nested_properties:
description:
- If true, flatten any nested properties into their dot notation names.
- For example 'summary["runtime"]["powerState"]' would become "summary.runtime.powerState"
type: bool
default: false
keyed_groups:
description:
- Use the values of the VMware object properties or other hostvars to create and populate groups.
type: list
default: []
search_paths:
description:
- Specify a list of paths that should be searched recursively for VMware objects.
- This effectively allows you to only include objects in certain datacenters, clusters, or folders.
- >-
Filtering is done before the initial object gathering query. If you have a large number of VMware objects, specifying
a subset of paths to search can help speed up the inventory plugin.
- The default value is an empty list, which means all paths (i.e. all datacenters) will be searched.
type: list
elements: str
default: []
group_by_paths:
description:
- If true, groups will be created based on the VMware object's paths.
- >-
Paths will be sanitized to match Ansible group name standards.
For example, any slashes or dashes in the paths will be replaced by underscores in the group names.
- A group is created for each step down in the path, with the group from the step above containing subsequent groups.
- For example, a path /DC-01/vms/Cluster will create groups 'DC_01' which contains group 'DC_01_vms' which contains group 'DC_01_vms_Cluster'
default: false
type: bool
group_by_paths_prefix:
description:
- If O(group_by_paths) is true, set this variable if you want to add a prefix to any groups created based on paths.
- By default, no prefix is added to the group names.
default: ''
type: str
sanitize_property_names:
description:
- If true, sanitize VMware object property names so they can safely be referenced within Ansible playbooks.
- This option also transforms property names to snake case. For example, powerState would become power_state.
type: bool
default: false

'''
208 changes: 14 additions & 194 deletions plugins/inventory/esxi_hosts.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,75 +26,10 @@
- vSphere Automation SDK (when gather_tags is True)

options:
gather_tags:
description:
- If true, gather any tags attached to the associated ESXi hosts
- Requires 'vSphere Automation SDK' library to be installed on the Ansible controller machine.
default: false
type: bool
hostnames:
description:
- A list of templates evaluated in order to compose inventory_hostname.
- Each value in the list should be a jinja template. You can see the examples section for more details.
- Templates that result in an empty string or None value are ignored and the next template is evaluated.
- You can use hostvars such as properties specified in O(properties) as variables in the template.
type: list
elements: string
default: ['name']
properties:
description:
- Specify a list of VMware schema properties associated with the ESXi hostsystem to collect and return as hostvars.
- Each value in the list can be a path to a specific property in hostsystem object or a path to a collection of hostsystem objects.
- Please make sure that if you use a property in another parameter that it is included in this option.
- Some properties are always returned, such as name, customValue, and summary.runtime.powerState
- Use V(all) to return all properties available for the ESXi host.
type: list
elements: string
default: ['name', 'customValue', 'summary.runtime.powerState']
flatten_nested_properties:
description:
- If true, flatten any nested properties into their dot notation names.
- For example 'summary["runtime"]["powerState"]' would become "summary.runtime.powerState"
type: bool
default: false
keyed_groups:
description:
- Use the values of ESXi host properties or other hostvars to create and populate groups.
type: list
default: [{key: 'summary.runtime.powerState', separator: ''}]
search_paths:
description:
- Specify a list of paths that should be searched recursively for hosts.
- This effectively allows you to only include hosts in certain datacenters, clusters, or folders.
- >-
Filtering is done before the initial host gathering query. If you have a large number of hosts, specifying
a subset of paths to search can help speed up the inventory plugin.
- The default value is an empty list, which means all paths (i.e. all datacenters) will be searched.
type: list
elements: str
default: []
group_by_paths:
description:
- If true, groups will be created based on the ESXI hosts' paths.
- >-
Paths will be sanitized to match Ansible group name standards.
For example, any slashes or dashes in the paths will be replaced by underscores in the group names.
- A group is created for each step down in the path, with the group from the step above containing subsequent groups.
- For example, a path /DC-01/hosts/Cluster will create groups 'DC_01' which contains group 'DC_01_hosts' which contains group 'DC_01_hosts_Cluster'
default: false
type: bool
group_by_paths_prefix:
description:
- If O(group_by_paths) is true, set this variable if you want to add a prefix to any groups created based on paths.
- By default, no prefix is added to the group names.
default: ''
type: str
sanitize_property_names:
description:
- If true, sanitize ESXi host property names so they can safely be referenced within Ansible playbooks.
- This option also transforms property names to snake case. For example, powerState would become power_state.
type: bool
default: false
"""

EXAMPLES = r"""
Expand Down Expand Up @@ -148,73 +83,27 @@
# Already handled in base class
pass

from ansible.errors import AnsibleError
from ansible.module_utils.common.text.converters import to_native
from ansible.module_utils.common.dict_transformations import camel_dict_to_snake_dict

from ansible_collections.vmware.vmware.plugins.inventory_utils._base import VmwareInventoryBase
from ansible_collections.vmware.vmware.plugins.module_utils._vmware_folder_paths import (
get_folder_path_of_vsphere_object
)
from ansible_collections.vmware.vmware.plugins.module_utils._vmware_facts import (
vmware_obj_to_json,
flatten_dict
from ansible_collections.vmware.vmware.plugins.inventory_utils._base import (
VmwareInventoryBase,
VmwareInventoryHost
)


class EsxiInventoryHost():
class EsxiInventoryHost(VmwareInventoryHost):
def __init__(self):
self.object = None
self.inventory_hostname = None
self.path = ''
self.properties = dict()
super().__init__()

Check warning on line 94 in plugins/inventory/esxi_hosts.py

View check run for this annotation

Codecov / codecov/patch

plugins/inventory/esxi_hosts.py#L94

Added line #L94 was not covered by tests
self._management_ip = None

@classmethod
def create_from_cache(cls, inventory_hostname, host_properties):
"""
Create the class from the inventory cache. We don't want to refresh the data or make any calls to vCenter.
Properties are populated from whatever we had previously cached.
"""
host = cls()
host.inventory_hostname = inventory_hostname
host.properties = host_properties
return host

@classmethod
def create_from_object(cls, host_object, properties_to_gather, pyvmomi_client):
def create_from_object(cls, vmware_object, properties_to_gather, pyvmomi_client):
"""
Create the class from a host object that we got from pyvmomi. The host properties will be populated
from the object and additional calls to vCenter
"""
host = cls()
host.object = host_object
host.path = get_folder_path_of_vsphere_object(host_object)
host.properties = host._set_properties_from_pyvmomi(properties_to_gather, pyvmomi_client)
host = super().create_from_object(vmware_object, properties_to_gather, pyvmomi_client)
host.properties['management_ip'] = host.management_ip

Check warning on line 104 in plugins/inventory/esxi_hosts.py

View check run for this annotation

Codecov / codecov/patch

plugins/inventory/esxi_hosts.py#L103-L104

Added lines #L103 - L104 were not covered by tests
return host

def _set_properties_from_pyvmomi(self, properties_to_gather, pyvmomi_client):
properties = vmware_obj_to_json(self.object, properties_to_gather)
properties['path'] = self.path
properties['management_ip'] = self.management_ip

# Custom values
if hasattr(self.object, "customValue"):
properties['customValue'] = dict()
field_mgr = pyvmomi_client.custom_field_mgr
for cust_value in self.object.customValue:
properties['customValue'][
[y.name for y in field_mgr if y.key == cust_value.key][0]
] = cust_value.value

return properties

def sanitize_properties(self):
self.properties = camel_dict_to_snake_dict(self.properties)

def flatten_properties(self):
self.properties = flatten_dict(self.properties)

@property
def management_ip(self):
# We already looked up the management IP from vcenter this session, so
Expand Down Expand Up @@ -243,6 +132,9 @@

return self._management_ip

def get_tags(self, rest_client):
return rest_client.get_tags_by_host_moid(self.object._GetMoId())

Check warning on line 136 in plugins/inventory/esxi_hosts.py

View check run for this annotation

Codecov / codecov/patch

plugins/inventory/esxi_hosts.py#L136

Added line #L136 was not covered by tests


class InventoryModule(VmwareInventoryBase):

Expand Down Expand Up @@ -313,7 +205,7 @@
for inventory_hostname, host_properties in cache_data.items():
esxi_host = EsxiInventoryHost.create_from_cache(
inventory_hostname=inventory_hostname,
host_properties=host_properties
properties=host_properties
)
self.__update_inventory(esxi_host)

Expand All @@ -332,15 +224,13 @@
continue

esxi_host = EsxiInventoryHost.create_from_object(
host_object=host_object,
vmware_object=host_object,
properties_to_gather=properties_to_gather,
pyvmomi_client=self.pyvmomi_client
)

if self.get_option("gather_tags"):
tags, tags_by_category = self.gather_tags(esxi_host.object._GetMoId())
esxi_host.properties["tags"] = tags
esxi_host.properties["tags_by_category"] = tags_by_category
self.add_tags_to_object_properties(esxi_host)

Check warning on line 233 in plugins/inventory/esxi_hosts.py

View check run for this annotation

Codecov / codecov/patch

plugins/inventory/esxi_hosts.py#L233

Added line #L233 was not covered by tests

self.set_inventory_hostname(esxi_host)
if esxi_host.inventory_hostname not in hostvars:
Expand All @@ -354,35 +244,6 @@
self.add_host_to_groups_based_on_path(esxi_host)
self.set_host_variables_from_host_properties(esxi_host)

def set_inventory_hostname(self, esxi_host):
"""
The user can specify a list of jinja templates, and the first valid template should be used for the
host's inventory hostname. The inventory hostname is mostly for decorative purposes since the
ansible_host value takes precedence when trying to connect.
"""
hostname = None
errors = []

for hostname_pattern in self.get_option("hostnames"):
try:
hostname = self._compose(template=hostname_pattern, variables=esxi_host.properties)
except Exception as e:
if self.get_option("strict"):
raise AnsibleError(
"Could not compose %s as hostnames - %s"
% (hostname_pattern, to_native(e))
)

errors.append((hostname_pattern, str(e)))
if hostname:
esxi_host.inventory_hostname = hostname
return

raise AnsibleError(
"Could not template any hostname for host, errors for each preference: %s"
% (", ".join(["%s: %s" % (pref, err) for pref, err in errors]))
)

def add_host_to_inventory(self, esxi_host: EsxiInventoryHost):
"""
Add the host to the inventory and any groups that the user wants to create based on inventory
Expand All @@ -398,44 +259,3 @@
self.get_option("groups"), esxi_host.properties, esxi_host.inventory_hostname, strict=strict)
self._add_host_to_keyed_groups(
self.get_option("keyed_groups"), esxi_host.properties, esxi_host.inventory_hostname, strict=strict)

def add_host_to_groups_based_on_path(self, esxi_host: EsxiInventoryHost):
"""
If the user desires, create groups based on each ESXi host's path. A group is created for each
step down in the path, with the group from the step above containing subsequent groups.
Optionally, the user can add a prefix to the groups created by this process.
The final group in the path will be where the ESXi host is added.
"""
if not self.get_option("group_by_paths"):
return

path_parts = esxi_host.path.split('/')
group_name_parts = []
last_created_group = None

if self.get_option("group_by_paths_prefix"):
group_name_parts = [self.get_option("group_by_paths_prefix")]

for path_part in path_parts:
if not path_part:
continue
group_name_parts.append(path_part)
group_name = self._sanitize_group_name('_'.join(group_name_parts))
group = self.inventory.add_group(group_name)

if last_created_group:
self.inventory.add_child(last_created_group, group)
last_created_group = group

if last_created_group:
self.inventory.add_host(esxi_host.inventory_hostname, last_created_group)

def set_host_variables_from_host_properties(self, esxi_host):
if self.get_option("sanitize_property_names"):
esxi_host.sanitize_properties()

if self.get_option("flatten_nested_properties"):
esxi_host.flatten_properties()

for k, v in esxi_host.properties.items():
self.inventory.set_variable(esxi_host.inventory_hostname, k, v)
Loading
Loading