Describe the bug
All three inventory plugins crash with 'NoneType' object has no attribute 'get' when processing VMs (or hosts) that have fields like ownership_info, project, or cluster set to None in the API response.
The root cause is the use of dict.get("key", {}).get(...) throughout the inventory plugins. In Python, dict.get("key", {}) only returns the default {} when the key is missing from the dict. If the key exists but its value is explicitly None (as returned by the SDK's to_dict()), Python returns None — and the subsequent .get() call fails.
Affected files:
plugins/inventory/ntnx_prism_vm_inventory_v2.py (6 locations)
plugins/inventory/ntnx_prism_vm_inventory.py (8 locations)
plugins/inventory/ntnx_prism_host_inventory_v2.py (6 locations)
To Reproduce
- Create a VM with minimal configuration (no description, no project, no ownership):
- name: Create minimal VM
nutanix.ncp.ntnx_vms:
nutanix_host: "{{ nutanix_host }}"
nutanix_username: "{{ nutanix_username }}"
nutanix_password: "{{ nutanix_password }}"
validate_certs: false
state: present
name: "test-vm-minimal"
cluster:
uuid: "{{ cluster_uuid }}"
vcpus: 1
cores_per_vcpu: 1
memory_gb: 1
- Run the inventory plugin:
ansible-inventory -i nutanix.yml --graph -vvv
- The plugin crashes when iterating over VMs because the SDK returns fields like
ownership_info: None, project: None in the VM dict.
Stack trace
[WARNING]: Failed to parse inventory with
'ansible_collections.nutanix.ncp.plugins.inventory.ntnx_prism_vm_inventory_v2'
plugin: 'NoneType' object has no attribute 'get'
Failed to parse inventory with
'ansible_collections.nutanix.ncp.plugins.inventory.ntnx_prism_vm_inventory_v2' plugin.
<<< caused by >>>
'NoneType' object has no attribute 'get'
Crash locations
ntnx_prism_vm_inventory_v2.py
# line 621
cluster_ext_id = vm.get("cluster", {}).get("ext_id") # crashes if cluster is None
# line 636-637
owner_ext_id = vm.get("ownership_info", {}).get("owner", {}).get("ext_id") # crashes if ownership_info is None
project_ext_id = vm.get("project", {}).get("ext_id") # crashes if project is None
# line 390-393 (_resolve_custom_ansible_host)
vm.get("cluster", {}).get("ext_id") # crashes if cluster is None
# line 599 (parse)
for cluster in list_clusters.data: # crashes if data is None
ntnx_prism_vm_inventory.py
# line 234-238
cluster = entity.get("status", {}).get("cluster_reference", {}).get("name") # crashes if status is None
vm_resources = entity.get("status", {}).get("resources", {}).copy() # crashes if status is None
# line 306-311
entity.get("metadata", {}).get("categories") # crashes if metadata is None
# line 328-330
filter_vars.update(entity.get("status", {})) # crashes if status is None
# line 437
entity.get("metadata", {}).get("project_reference", {}) # crashes if metadata is None
ntnx_prism_host_inventory_v2.py
# line 343-351
hypervisor = host.get("hypervisor", {}) # returns None if hypervisor key exists with None value
external_addr = hypervisor.get("external_address", {}) # crashes
controller_vm = host.get("controller_vm", {}) # same issue
external_addr = controller_vm.get("external_address", {}) # crashes
# line 507-508
cluster_ext_id = host.get("cluster", {}).get("uuid") # crashes if cluster is None
cluster_name = host.get("cluster", {}).get("name") # crashes if cluster is None
Expected behavior
The inventory plugins should gracefully handle None values for nested fields and continue processing VMs/hosts without crashing.
Fix
Replace all unsafe dict.get("key", {}).get(...) patterns with (dict.get("key") or {}).get(...). The or {} ensures None values are coerced to an empty dict before the chained .get() call.
Example:
# Before (crashes when ownership_info is None)
owner_ext_id = vm.get("ownership_info", {}).get("owner", {}).get("ext_id")
# After (safe)
owner_ext_id = ((vm.get("ownership_info") or {}).get("owner") or {}).get("ext_id")
Proof:
>>> vm = {"ownership_info": None, "project": None, "cluster": None}
>>> # OLD CODE - crashes
>>> vm.get("ownership_info", {}).get("owner", {}).get("ext_id")
AttributeError: 'NoneType' object has no attribute 'get'
>>> # NEW CODE - safe
>>> ((vm.get("ownership_info") or {}).get("owner") or {}).get("ext_id")
None # returns None gracefully
Additional context
- Affects
nutanix.ncp collection version 2.4.0
- Reproduced with
ansible-core 2.20.0 (Python 3.13) and ansible-core 2.16.0 (Python 3.10)
- The issue manifests when VMs are created with minimal configuration (common in automated/test environments) or when the PC returns
None for optional nested fields
- All 3 inventory plugins (
ntnx_prism_vm_inventory, ntnx_prism_vm_inventory_v2, ntnx_prism_host_inventory_v2) are affected by the same pattern
Describe the bug
All three inventory plugins crash with
'NoneType' object has no attribute 'get'when processing VMs (or hosts) that have fields likeownership_info,project, orclusterset toNonein the API response.The root cause is the use of
dict.get("key", {}).get(...)throughout the inventory plugins. In Python,dict.get("key", {})only returns the default{}when the key is missing from the dict. If the key exists but its value is explicitlyNone(as returned by the SDK'sto_dict()), Python returnsNone— and the subsequent.get()call fails.Affected files:
plugins/inventory/ntnx_prism_vm_inventory_v2.py(6 locations)plugins/inventory/ntnx_prism_vm_inventory.py(8 locations)plugins/inventory/ntnx_prism_host_inventory_v2.py(6 locations)To Reproduce
ownership_info: None,project: Nonein the VM dict.Stack trace
Crash locations
ntnx_prism_vm_inventory_v2.py
ntnx_prism_vm_inventory.py
ntnx_prism_host_inventory_v2.py
Expected behavior
The inventory plugins should gracefully handle
Nonevalues for nested fields and continue processing VMs/hosts without crashing.Fix
Replace all unsafe
dict.get("key", {}).get(...)patterns with(dict.get("key") or {}).get(...). Theor {}ensuresNonevalues are coerced to an empty dict before the chained.get()call.Example:
Proof:
Additional context
nutanix.ncpcollection version 2.4.0ansible-core 2.20.0(Python 3.13) andansible-core 2.16.0(Python 3.10)Nonefor optional nested fieldsntnx_prism_vm_inventory,ntnx_prism_vm_inventory_v2,ntnx_prism_host_inventory_v2) are affected by the same pattern