We have some news to share for the request index beta feature. We’ve added more options to sort your requests, counters to the individual filters and documentation for the search functionality. Checkout the blog post for more details.

File implement-multiple-inventory-for-ansible.targets.patch of Package salt

From f60cc567c1c3d849d14fa547e87ca369bbbe1d2b Mon Sep 17 00:00:00 2001
From: Victor Zhestkov <vzhestkov@suse.com>
Date: Mon, 10 Mar 2025 13:25:56 +0100
Subject: [PATCH] Implement multiple inventory for `ansible.targets`

* Implement multiple inventory for ansible.targets

* Add tests for multiple inventories with ansible.targets
---
 salt/modules/ansiblegate.py                   |  10 +-
 salt/utils/ansible.py                         |  61 ++++++---
 .../pytests/unit/modules/test_ansiblegate.py  | 119 ++++++++++++++++++
 3 files changed, 169 insertions(+), 21 deletions(-)

diff --git a/salt/modules/ansiblegate.py b/salt/modules/ansiblegate.py
index 920c374e5a..9dd878665f 100644
--- a/salt/modules/ansiblegate.py
+++ b/salt/modules/ansiblegate.py
@@ -423,7 +423,7 @@ def playbooks(
     return retdata
 
 
-def targets(inventory="/etc/ansible/hosts", yaml=False, export=False):
+def targets(inventory=None, inventories=None, yaml=False, export=False):
     """
     .. versionadded:: 3005
 
@@ -432,6 +432,10 @@ def targets(inventory="/etc/ansible/hosts", yaml=False, export=False):
     :param inventory:
         The inventory file to read the inventory from. Default: "/etc/ansible/hosts"
 
+    :param inventories:
+        The list of inventory files to read the inventory from.
+        Uses `inventory` in case if `inventories` is not specified.
+
     :param yaml:
         Return the inventory as yaml output. Default: False
 
@@ -446,7 +450,9 @@ def targets(inventory="/etc/ansible/hosts", yaml=False, export=False):
         salt 'ansiblehost' ansible.targets inventory=my_custom_inventory
 
     """
-    return salt.utils.ansible.targets(inventory=inventory, yaml=yaml, export=export)
+    return salt.utils.ansible.targets(
+        inventory=inventory, inventories=inventories, yaml=yaml, export=export
+    )
 
 
 def discover_playbooks(
diff --git a/salt/utils/ansible.py b/salt/utils/ansible.py
index b91c931dff..2c85da4753 100644
--- a/salt/utils/ansible.py
+++ b/salt/utils/ansible.py
@@ -18,32 +18,55 @@ def __virtual__():  # pylint: disable=expected-2-blank-lines-found-0
     return (False, "Install `ansible` to use inventory")
 
 
-def targets(inventory="/etc/ansible/hosts", yaml=False, export=False):
+def targets(inventory=None, inventories=None, yaml=False, export=False):
     """
     Return the targets from the ansible inventory_file
     Default: /etc/salt/roster
     """
-    if not os.path.isfile(inventory):
-        raise CommandExecutionError("Inventory file not found: {}".format(inventory))
-    if not os.path.isabs(inventory):
-        raise CommandExecutionError("Path to inventory file must be an absolute path")
+
+    if inventory is None and inventories is None:
+        inventory = "/etc/ansible/hosts"
+    multi_inventory = True
+    if not isinstance(inventories, list):
+        multi_inventory = False
+        inventories = []
+    if inventory is not None and inventory not in inventories:
+        inventories.append(inventory)
 
     extra_cmd = []
     if export:
         extra_cmd.append("--export")
     if yaml:
         extra_cmd.append("--yaml")
-    inv = salt.modules.cmdmod.run(
-        "ansible-inventory -i {} --list {}".format(inventory, " ".join(extra_cmd)),
-        env={"ANSIBLE_DEPRECATION_WARNINGS": "0"},
-        reset_system_locale=False,
-    )
-    if yaml:
-        return salt.utils.stringutils.to_str(inv)
-    else:
-        try:
-            return salt.utils.json.loads(salt.utils.stringutils.to_str(inv))
-        except ValueError:
-            raise CommandExecutionError(
-                "Error processing the inventory: {}".format(inv)
-            )
+
+    ret = {}
+
+    for inventory in inventories:
+        if not os.path.isfile(inventory):
+            raise CommandExecutionError("Inventory file not found: {}".format(inventory))
+        if not os.path.isabs(inventory):
+            raise CommandExecutionError("Path to inventory file must be an absolute path")
+
+        inv = salt.modules.cmdmod.run(
+            "ansible-inventory -i {} --list {}".format(inventory, " ".join(extra_cmd)),
+            env={"ANSIBLE_DEPRECATION_WARNINGS": "0"},
+            reset_system_locale=False,
+        )
+
+        if yaml:
+            inv = salt.utils.stringutils.to_str(inv)
+        else:
+            try:
+                inv = salt.utils.json.loads(salt.utils.stringutils.to_str(inv))
+            except ValueError:
+                raise CommandExecutionError(
+                    "Error processing the inventory {}: {}".format(inventory, inv)
+                )
+
+        if not multi_inventory:
+            ret = inv
+            break
+
+        ret[inventory] = inv
+
+    return ret
diff --git a/tests/pytests/unit/modules/test_ansiblegate.py b/tests/pytests/unit/modules/test_ansiblegate.py
index 272da721bf..d8bdd1140e 100644
--- a/tests/pytests/unit/modules/test_ansiblegate.py
+++ b/tests/pytests/unit/modules/test_ansiblegate.py
@@ -189,6 +189,125 @@ def test_ansible_targets(minion_opts):
             assert len(ret["ungrouped"]["hosts"]) == 2
 
 
+def test_ansible_targets_multiple_inventories(minion_opts):
+    """
+    Test ansible.targets execution module function with multiple inventories.
+    :return:
+    """
+    ansible_inventory1_ret = """
+{
+    "_meta": {
+        "hostvars": {
+            "uyuni-stable-ansible-centos7-1.tf.local": {
+                "ansible_ssh_private_key_file": "/etc/ansible/my_ansible_private_key"
+            },
+            "uyuni-stable-ansible-centos7-2.tf.local": {
+                "ansible_ssh_private_key_file": "/etc/ansible/my_ansible_private_key"
+            }
+        }
+    },
+    "all": {
+        "children": [
+            "ungrouped"
+        ]
+    },
+    "ungrouped": {
+        "hosts": [
+            "uyuni-stable-ansible-centos7-1.tf.local",
+            "uyuni-stable-ansible-centos7-2.tf.local"
+        ]
+    }
+}
+    """
+    ansible_inventory2_ret = """
+{
+    "_meta": {
+        "hostvars": {
+            "uyuni-stable-ansible-alma9-1.tf.local": {
+                "ansible_ssh_private_key_file": "/etc/ansible/my_ansible_private_key"
+            },
+            "uyuni-stable-ansible-alma9-2.tf.local": {
+                "ansible_ssh_private_key_file": "/etc/ansible/my_ansible_private_key"
+            }
+        }
+    },
+    "all": {
+        "children": [
+            "ungrouped"
+        ]
+    },
+    "ungrouped": {
+        "hosts": [
+            "uyuni-stable-ansible-alma9-1.tf.local",
+            "uyuni-stable-ansible-alma9-2.tf.local"
+        ]
+    }
+}
+    """
+    ansible_inventory_mock = MagicMock(
+        side_effect=[ansible_inventory1_ret, ansible_inventory2_ret]
+    )
+    with patch("salt.utils.path.which", MagicMock(return_value=True)):
+        utils = salt.loader.utils(minion_opts, whitelist=["ansible"])
+        with patch("salt.modules.cmdmod.run", ansible_inventory_mock), patch.dict(
+            ansiblegate.__utils__, utils
+        ), patch("os.path.isfile", MagicMock(return_value=True)):
+            ret = ansiblegate.targets(
+                inventories=["/etc/ansible/hosts1", "/etc/ansible/hosts2"]
+            )
+            assert ansible_inventory_mock.call_args
+            assert ansible_inventory_mock.call_args
+            assert len(ret.keys()) == 2
+            assert "/etc/ansible/hosts1" in ret.keys()
+            assert "/etc/ansible/hosts2" in ret.keys()
+            assert "_meta" in ret["/etc/ansible/hosts1"]
+            assert "_meta" in ret["/etc/ansible/hosts2"]
+            assert (
+                "uyuni-stable-ansible-centos7-1.tf.local"
+                in ret["/etc/ansible/hosts1"]["_meta"]["hostvars"]
+            )
+            assert (
+                "uyuni-stable-ansible-centos7-2.tf.local"
+                in ret["/etc/ansible/hosts1"]["_meta"]["hostvars"]
+            )
+            assert (
+                "uyuni-stable-ansible-alma9-1.tf.local"
+                in ret["/etc/ansible/hosts2"]["_meta"]["hostvars"]
+            )
+            assert (
+                "uyuni-stable-ansible-alma9-2.tf.local"
+                in ret["/etc/ansible/hosts2"]["_meta"]["hostvars"]
+            )
+            assert (
+                "ansible_ssh_private_key_file"
+                in ret["/etc/ansible/hosts1"]["_meta"]["hostvars"][
+                    "uyuni-stable-ansible-centos7-1.tf.local"
+                ]
+            )
+            assert (
+                "ansible_ssh_private_key_file"
+                in ret["/etc/ansible/hosts1"]["_meta"]["hostvars"][
+                    "uyuni-stable-ansible-centos7-2.tf.local"
+                ]
+            )
+            assert (
+                "ansible_ssh_private_key_file"
+                in ret["/etc/ansible/hosts2"]["_meta"]["hostvars"][
+                    "uyuni-stable-ansible-alma9-1.tf.local"
+                ]
+            )
+            assert (
+                "ansible_ssh_private_key_file"
+                in ret["/etc/ansible/hosts2"]["_meta"]["hostvars"][
+                    "uyuni-stable-ansible-alma9-2.tf.local"
+                ]
+            )
+            assert "all" in ret["/etc/ansible/hosts1"]
+            assert "all" in ret["/etc/ansible/hosts2"]
+            assert len(ret["/etc/ansible/hosts1"]["ungrouped"]["hosts"]) == 2
+            assert len(ret["/etc/ansible/hosts2"]["ungrouped"]["hosts"]) == 2
+
+
 def test_ansible_discover_playbooks_single_path():
     playbooks_dir = os.path.join(
         RUNTIME_VARS.TESTS_DIR, "unit/files/playbooks/example_playbooks/"
-- 
2.48.1

openSUSE Build Service is sponsored by