ansible-saltstack-inventory exposes the minions known live to a local Salt
master as an Ansible dynamic inventory. It is intended for the awkward but
common middle period where Salt still knows the estate while Ansible is being
introduced for one-off operations, staged migration, or eventual replacement.
The executable keeps Salt and Ansible at a process boundary. Salt can run in
its supported bundled Python while a current ansible-core runs in a different
control-node Python. Ansible only consumes the JSON written by inventory.py.
$ ./inventory.py --list
{
"_meta": {
"hostvars": {
"web-01.example.test": {
"os": "Ubuntu",
"roles": ["web", "migration"]
}
}
},
"all": {"children": ["ungrouped"]},
"ungrouped": {"hosts": ["web-01.example.test"]}
}--listruns onegrains.itemscommand against*. Each responding minion becomes an ungrouped Ansible host, and its grains become direct host variables.--host HOSTperforms an exact Saltlisttarget and returns that minion's grains. It exists for the inventory-script contract; Ansible normally uses_meta.hostvarsfrom--listinstead.- An empty Salt response is a valid empty inventory. A malformed response, Salt exception, or value that cannot be represented as strict JSON is an error and produces no inventory on stdout.
The script intentionally does not turn grains into groups. That keeps the
external interface small and avoids imposing one organization's meaning of
roles, environment, or nodegroups on another.
Run the inventory on the Salt master with permission to use
salt.client.LocalClient.
In the normal local-authentication case, that means the Salt master service
user or another account with access to the master's configuration and sockets.
Remote salt-api access is not implemented.
Clone the repository and confirm that its checked-in executable can query Salt:
git clone https://github.com/beckness/ansible-saltstack-inventory.git
cd ansible-saltstack-inventory
./inventory.py --listCurrent Salt packages use an isolated onedir Python. If /usr/bin/env python3
cannot import salt, export the absolute path of the one Salt-capable Python
interpreter before invoking either the script or Ansible:
export SALTSTACK_INVENTORY_PYTHON=/opt/saltstack/salt/bin/python3
./inventory.py --list
ansible-inventory --inventory ./inventory.py --list
ansible-playbook --inventory ./inventory.py migration.ymlSALTSTACK_INVENTORY_PYTHON is one executable path, not a shell command. Do
not append arguments such as python or -m; the script passes its filename
and inventory arguments directly to that interpreter without invoking a shell.
Confirm the actual onedir path supplied by your Salt package if it differs from
the example.
For a pip-based Salt development environment, the optional dependency metadata
is available as .[salt]:
python3 -m venv .venv
. .venv/bin/activate
python -m pip install -e '.[salt,test]'Salt describes pip installations as
reasonable-effort rather than fully supported.
Production users should prefer Salt's supported packages and their bundled
interpreter.
Grains are direct host variables, so playbooks can read os, roles, ipv4,
and custom grains immediately. If persistent groups are useful, layer
Ansible's built-in
constructed inventory plugin
after the script instead of modifying the producer. For example, save this as
constructed.yml:
plugin: ansible.builtin.constructed
strict: false
keyed_groups:
- prefix: salt_os
key: os
- prefix: salt_role
key: rolesThen load the Salt source first:
ansible-inventory -i ./inventory.py -i ./constructed.yml --graph
ansible-playbook -i ./inventory.py -i ./constructed.yml migration.ymlAnsible also maintains a
community.general.saltstack connection plugin
for executing tasks through Salt instead of SSH. It is complementary, not an
inventory source, and it imports Salt inside Ansible's process; use it only
when its shared-runtime requirements fit the control node.
- Inventory contains minions that return
grains.itemswithin Salt's configured timeout. Accepted keys, cached grains, and offline minions are not enumerated. - All grains are exposed directly as Ansible host variables. Custom grains can
collide with Ansible variable names or contain information that should not be
copied into logs, fact caches, or
ansible-inventoryoutput. Audit grains for the environment where this is used. - The script does not cache results, configure external authentication, select subsets, derive groups, or contact a remote master.
- A missing response for
--hostis{}; failures talking to Salt are nonzero errors. Operators should distinguish a genuinely empty estate from an unexpectedly unreachable one before running broad migration playbooks.
This matrix records CI evidence as of 2026-08-10. It is deliberately not a claim that every Salt, Python, and Ansible version was installed together: Ansible executes the inventory as a separate process, so the producer and consumer runtimes do not need to match.
| Boundary | CI coverage | Confidence |
|---|---|---|
| Script logic | Python 3.11, 3.12, 3.13, 3.14 | Fixture tests exercise CLI, validation, JSON, and interpreter handoff. |
| Salt producer | Salt 3006.27 / Python 3.11 | Real Salt import and LocalClient.cmd API; fixture-backed call, no live master. |
| Salt producer | Salt 3008.2 / Python 3.14 | Real Salt import and LocalClient.cmd API; fixture-backed call, no live master. |
| Ansible consumer | ansible-core 2.20 / Python 3.12 | ansible-inventory parses the executable's fixture-backed output. |
| Ansible consumer | ansible-core 2.21 / Python 3.13 | ansible-inventory parses the executable's fixture-backed output. |
| Ansible consumer | ansible-core 2.21 / Python 3.14 | ansible-inventory parses the executable's fixture-backed output. |
Salt 3008 is the current LTS line. Salt 3006 is included for migration estates
while it remains in CVE/critical support; unsupported Salt 3007 and releases
older than 3006 are excluded. See the upstream
Salt support lifecycle
and ansible-core support matrix
for dates that will outlive this snapshot.
Ansible recommends inventory plugins for integrations that need rich
configuration, caching, or constructed grouping, but continues to support the
--list / --host script contract.
This tool currently needs only a local Salt query and JSON process boundary, so
a repository-specific plugin would add installation and discovery machinery
without adding capability.
A bounded audit on 2026-08-10 found no maintained drop-in Salt inventory in
Ansible's inventory plugin index
or the likely public GitHub candidates. The
hbonath fork only
applied Python 3 syntax in 2021. The separate
TrevorBenson implementation
was last changed in 2022 and places required groups under _meta, which is not
valid current inventory JSON. This is a bounded finding, not a guarantee that
no private or newly published implementation exists.
A proper inventory plugin becomes worthwhile if this project later adds remote-master configuration, caching, filters, automatic group policy, or Ansible-native configuration files.
The normal suite needs neither Salt nor Ansible:
python -m pip install -e '.[test]'
pytest -m 'not producer and not ansible'Recorded grains live in tests/fixtures/grains.json. A test-only fake
salt.client.LocalClient drives executable and Ansible contract tests. The two
producer CI jobs install the named Salt versions and replace only the live
master interaction; therefore CI proves import and API compatibility, not an
end-to-end master/minion deployment.
The project is licensed under the MIT License.