1
0
Fork 0
mirror of https://github.com/ansible-collections/community.general.git synced 2026-07-08 11:39:02 +00:00

xml: fix predicated xpath no-match incorrectly creating nodes (#12031)

* fix(xml): no-op when predicated xpath finds no match instead of creating nodes

When using xpath like element[text()='old'] with value=new, a no-match due
to the predicate not being satisfied incorrectly triggered node creation,
corrupting the XML. Now treats predicate misses as a no-op.

Fixes #8730

* changelog(xml): add fragment for PR #12031

* fix(xml): remove spurious test-unset-element-value include from main.yml

That file belongs to a different branch and was accidentally dragged in
during a stash conflict resolution.

* feat(xml): add create_if_missing option to control node creation on value no-match

Instead of implicitly creating nodes when value is set and xpath finds no match,
expose create_if_missing (default true, preserving old behavior) so callers
can opt into a silent no-op with create_if_missing=false.

Fixes #8730
This commit is contained in:
Alexei Znamensky 2026-05-17 20:48:56 +12:00 committed by GitHub
parent bb93811ea6
commit 15616be827
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 139 additions and 4 deletions

View file

@ -144,6 +144,13 @@ options:
- This parameter requires O(xpath) to be set.
type: bool
default: false
create_if_missing:
description:
- When using O(value) and the O(xpath) matches no nodes, create the node.
- When set to V(false), a no-match is silently ignored instead of creating a new node.
type: bool
default: true
version_added: "13.0.0"
requirements:
- lxml >= 2.3.0
notes:
@ -673,11 +680,13 @@ def ensure_xpath_exists(module, tree, xpath, namespaces):
finish(module, tree, xpath, namespaces, changed)
def set_target_inner(module, tree, xpath, namespaces, attribute, value):
def set_target_inner(module, tree, xpath, namespaces, attribute, value, create_if_missing=True):
changed = False
try:
if not is_node(tree, xpath, namespaces):
if not create_if_missing:
return changed
changed = check_or_make_target(module, tree, xpath, namespaces)
except Exception as e:
missing_namespace = ""
@ -722,8 +731,8 @@ def set_target_inner(module, tree, xpath, namespaces, attribute, value):
return changed
def set_target(module, tree, xpath, namespaces, attribute, value):
changed = set_target_inner(module, tree, xpath, namespaces, attribute, value)
def set_target(module, tree, xpath, namespaces, attribute, value, create_if_missing):
changed = set_target_inner(module, tree, xpath, namespaces, attribute, value, create_if_missing)
finish(module, tree, xpath, namespaces, changed)
@ -895,6 +904,7 @@ def main():
huge_tree=dict(type="bool", default=False),
insertbefore=dict(type="bool", default=False),
insertafter=dict(type="bool", default=False),
create_if_missing=dict(type="bool", default=True),
),
supports_check_mode=True,
required_by=dict(
@ -939,6 +949,7 @@ def main():
huge_tree = module.params["huge_tree"]
insertbefore = module.params["insertbefore"]
insertafter = module.params["insertafter"]
create_if_missing = module.params["create_if_missing"]
# Check if we have lxml 2.3.0 or newer installed
if not HAS_LXML:
@ -1014,7 +1025,7 @@ def main():
# Is the xpath target an attribute selector?
if value is not None:
set_target(module, doc, xpath, namespaces, attribute, value)
set_target(module, doc, xpath, namespaces, attribute, value, create_if_missing)
# If an xpath was provided, we need to do something with the data
if xpath is not None: