diff --git a/plugins/module_utils/infrahub_utils.py b/plugins/module_utils/infrahub_utils.py index 392a173..d5b2a2a 100644 --- a/plugins/module_utils/infrahub_utils.py +++ b/plugins/module_utils/infrahub_utils.py @@ -1298,8 +1298,11 @@ def _get_object( if not node_id and not node_hfid: return None + include = [key for key in data if key not in ("id", "hfid")] or None try: - node = self.client.fetch_single_node(kind=kind, id=node_id, hfid=node_hfid, raise_when_missing=False) + node = self.client.fetch_single_node( + kind=kind, id=node_id, hfid=node_hfid, include=include, raise_when_missing=False + ) except Exception as exc: self._handle_errors(f"An error occurred while retrieving {kind} {data} due to {exc}") diff --git a/tests/unit/plugins/module_utils/test_node.py b/tests/unit/plugins/module_utils/test_node.py index eef8de5..8a3db04 100644 --- a/tests/unit/plugins/module_utils/test_node.py +++ b/tests/unit/plugins/module_utils/test_node.py @@ -540,6 +540,54 @@ def test_update_real_checksum_mismatch_triggers_upload(self, tmp_path): existing_node.save.assert_called_once() +# --------------------------------------------------------------------------- +# Regression: _get_object must pass user data keys as include on fetch. +# Without include, cardinality-MANY relationships stay uninitialized on the +# fetched node and are missing from the "before" diff snapshot, so idempotent +# re-runs report changed: true (fixed in 6f1315e, lost in 0142ad6). +# --------------------------------------------------------------------------- + + +class TestGetObjectIncludesUserKeys: + def test_fetch_includes_user_data_keys(self): + """_get_object passes user field names as include, excluding id/hfid.""" + mock_client = make_mock_client(existing_node=make_mock_node()) + mock_module = make_mock_module() + + node_module = NodeModule(module=mock_module, client=mock_client) + data = { + "id": "uuid-1234", + "name": {"value": "tag1"}, + "description": {"value": "managed by ansible"}, + "related_tags": [{"id": "uuid-5678"}], + } + node_module._get_object(schema=MagicMock(), kind="BuiltinTag", data=data) + + mock_client.fetch_single_node.assert_called_once_with( + kind="BuiltinTag", + id="uuid-1234", + hfid=None, + include=["name", "description", "related_tags"], + raise_when_missing=False, + ) + + def test_fetch_identifier_only_defaults_include_to_none(self): + """Identifier-only data (e.g. state: absent) keeps default fetch behavior.""" + mock_client = make_mock_client(existing_node=make_mock_node()) + mock_module = make_mock_module() + + node_module = NodeModule(module=mock_module, client=mock_client) + node_module._get_object(schema=MagicMock(), kind="BuiltinTag", data={"id": "uuid-1234"}) + + mock_client.fetch_single_node.assert_called_once_with( + kind="BuiltinTag", + id="uuid-1234", + hfid=None, + include=None, + raise_when_missing=False, + ) + + # --------------------------------------------------------------------------- # Schema-based version gating (CoreFileObject inherit_from validation) # ---------------------------------------------------------------------------