Merge "is-empty inspection rule to handle missing field"

This commit is contained in:
Zuul 2025-12-11 12:15:42 +00:00 committed by Gerrit Code Review
commit 9fdd30545c
3 changed files with 68 additions and 9 deletions

View file

@ -106,7 +106,7 @@ class Base(object):
_("args must be either a list or dictionary"))
def interpolate_variables(value, node, inventory, plugin_data,
loop_context=None):
loop_context=None, op=None):
loop_context = loop_context or {}
format_context = {
'node': node,
@ -115,12 +115,20 @@ class Base(object):
**loop_context
}
def safe_format(val, context):
def safe_format(val, context, op=None):
if isinstance(val, str):
try:
return val.format(**context)
except (AttributeError, KeyError, ValueError, IndexError,
TypeError) as e:
if isinstance(e, KeyError):
# Treat missing fields as empty for 'is-empty'.
if op == 'is-empty':
LOG.debug(
"Interpolation failed (missing field) "
"for 'is-empty': %(value)s, returning None",
{'value': val})
return None
LOG.warning(
"Interpolation failed: %(value)s: %(error_class)s, "
"%(error)s", {'value': val,
@ -148,21 +156,21 @@ class Base(object):
# 'path': 'driver_info/ipmi_address',
# 'value': '{inventory[bmc_address]}'
# }
value = safe_format(value, format_context)
value = safe_format(value, format_context, op)
if isinstance(value, str):
return safe_format(value, format_context)
return safe_format(value, format_context, op)
elif isinstance(value, dict):
return {
safe_format(k, format_context): Base.interpolate_variables(
v, node, inventory, plugin_data, loop_context)
safe_format(k, format_context, op): Base.interpolate_variables(
v, node, inventory, plugin_data, loop_context, op)
for k, v in value.items()
}
elif isinstance(value, list):
return [
safe_format(v, format_context) if isinstance(v, str)
safe_format(v, format_context, op) if isinstance(v, str)
else Base.interpolate_variables(
v, node, inventory, plugin_data, loop_context)
v, node, inventory, plugin_data, loop_context, op)
for v in value
]
return value
@ -192,7 +200,7 @@ class Base(object):
formatted_args = getattr(self, 'FORMATTED_ARGS', [])
return {
k: (Base.interpolate_variables(
v, node, inventory, plugin_data, loop_context)
v, node, inventory, plugin_data, loop_context, op)
if (k in formatted_args or loop_context) else v)
for k, v in dict_args.items()
}

View file

@ -526,6 +526,37 @@ class TestOperators(TestInspectionRules):
result = op(task, **test_cases[1])
self.assertFalse(result)
def test_is_empty_with_missing_field(self):
"""Test is-empty condition with missing field in inventory."""
with task_manager.acquire(self.context, self.node.uuid) as task:
condition = {
'op': 'is-empty',
'args': {'value': '{inventory[i.do.not.exist]}'}
}
op = inspection_rules.operators.EmptyOperator()
result = op.check_condition(task, condition, self.inventory,
self.plugin_data)
self.assertTrue(result)
condition2 = {
'op': 'is-empty',
'args': {'value': '{inventory[bmc_address]}'}
}
result2 = op.check_condition(task, condition2, self.inventory,
self.plugin_data)
self.assertFalse(result2)
test_inventory = self.inventory.copy()
test_inventory['empty_field'] = ''
condition3 = {
'op': 'is-empty',
'args': {'value': '{inventory[empty_field]}'}
}
result3 = op.check_condition(task, condition3, test_inventory,
self.plugin_data)
self.assertTrue(result3)
class TestActions(TestInspectionRules):
"""Test inspection rule actions"""
@ -1027,6 +1058,17 @@ class TestInterpolation(TestInspectionRules):
value, task.node, self.inventory, self.plugin_data)
self.assertEqual(value, result)
value = "{inventory[i.do.not.exist]}"
result = base.Base.interpolate_variables(
value, task.node, self.inventory, self.plugin_data,
op='is-empty')
self.assertIsNone(result)
value = "{inventory[missing][key]}"
result = base.Base.interpolate_variables(
value, task.node, self.inventory, self.plugin_data, op='eq')
self.assertEqual(value, result)
class TestValidation(TestInspectionRules):
def test_unsupported_operator_rejected(self):

View file

@ -0,0 +1,9 @@
---
fixes:
- |
Fixes the ``is-empty`` inspection rule to properly handle missing fields
in the inventory. Previously, when checking for empty values using fields
that don't exist in the inventory, the rule would fail with a KeyError
during variable interpolation. Now, missing fields are treated as empty
for the ``is-empty`` operation, allowing the rule to pass when checking
for non-existent inventory fields.