Skip to content

About

Ansible dynamic inventory script for SaltStack

Resources

Stars

0 stars

Watchers

1 watching

Forks

Latest commit

 

History

5 Commits

Folders and files

Repository files navigation

Ansible inventory from Salt node knowledge

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.

What it returns

$ ./inventory.py --list
{
  "_meta": {
    "hostvars": {
      "web-01.example.test": {
        "os": "Ubuntu",
        "roles": ["web", "migration"]
      }
    }
  },
  "all": {"children": ["ungrouped"]},
  "ungrouped": {"hosts": ["web-01.example.test"]}
}
  • --list runs one grains.items command against *. Each responding minion becomes an ungrouped Ansible host, and its grains become direct host variables.
  • --host HOST performs an exact Salt list target and returns that minion's grains. It exists for the inventory-script contract; Ansible normally uses _meta.hostvars from --list instead.
  • 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.

Requirements and installation

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 --list

Current 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.yml

SALTSTACK_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.

Building useful Ansible groups

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: roles

Then load the Salt source first:

ansible-inventory -i ./inventory.py -i ./constructed.yml --graph
ansible-playbook -i ./inventory.py -i ./constructed.yml migration.yml

Ansible 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.

Operational boundaries

  • Inventory contains minions that return grains.items within 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-inventory output. 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 --host is {}; failures talking to Salt are nonzero errors. Operators should distinguish a genuinely empty estate from an unexpectedly unreachable one before running broad migration playbooks.

Compatibility

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.

Why this remains an inventory script

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.

Development

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.

About

Ansible dynamic inventory script for SaltStack

Resources

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages