Skip to main content
PathMon

Chapter 3 of 6

Ansible Dynamic Inventory

Updated Read the full guide

The patchmon.dynamic_inventory Ansible plugin queries PatchMon's scoped integration API and turns it into a live Ansible inventory. Hosts and their group memberships stay in sync with PatchMon automatically, so you stop hand-editing hosts.ini.

Related pages:


What the plugin does

For each request, the plugin:

  1. Calls GET /api/v1/api/hosts on your PatchMon instance with HTTP Basic Auth.
  2. Receives a JSON list of active hosts, their IPs, and their PatchMon host-group memberships.
  3. Builds an Ansible inventory in memory:
    • Each PatchMon host becomes an Ansible host, keyed by hostname.
    • ansible_host is set to the host's ip field (so Ansible connects directly to the IP even if DNS is iffy).
    • Each PatchMon host group becomes an Ansible group, and the host is added as a member.

The result is a fully dynamic ansible-inventory --list tree driven entirely by PatchMon's groupings.


Requirements

Component Minimum version
Ansible 2.19.0
Python 3.6
requests 2.25.1

Install the Python dependency on the machine running ansible:

pip install 'requests>=2.25.1'

Installation

ansible-galaxy collection install patchmon.dynamic_inventory

From source

git clone https://github.com/PatchMon/PatchMon-ansible.git
cd PatchMon-ansible/patchmon/dynamic_inventory

# Build the collection tarball
ansible-galaxy collection build

# Install it locally
ansible-galaxy collection install patchmon-dynamic_inventory-*.tar.gz

# Install Python dependencies
pip install -r requirements.txt

Creating an API Credential in PatchMon

The plugin authenticates as an integration API credential (one of the scoped Basic-Auth tokens managed by PatchMon's integration API). It is not a normal user password.

  1. Sign in to PatchMon as a user with can_manage_settings.
  2. Go to Settings → Integrations and select the API tab.
  3. Click Create API Key and fill in:
    • Name: e.g. Ansible inventory
    • Scopes: at minimum, host:read. If you want the plugin to read host stats as well, add the other read scopes. See Integration API Documentation for the full scope list.
    • Allowed IP addresses (optional): restrict the credential to the public IP of your Ansible controller.
    • Expiration (optional): set a date if the credential is temporary.
  4. Click Create.
  5. Copy the secret immediately. It is displayed only once. Save both the Token Key (the username) and Token Secret (the password).

The plugin's api_key config value is PatchMon's Token Key. The plugin's api_secret is PatchMon's Token Secret. The labels differ; the meaning is the same.


Configuration

Create an inventory file, e.g. patchmon_inventory.yml:

---
plugin: patchmon.dynamic_inventory
api_url: https://patchmon.example.com/api/v1/api/hosts/
api_key: your_token_key
api_secret: your_token_secret
verify_ssl: true

Configuration options

Option Required Default Description
plugin yes (required) Must be patchmon.dynamic_inventory
api_url yes (required) URL of the PatchMon scoped hosts endpoint. For PatchMon 2.x this is https://<your-patchmon-host>/api/v1/api/hosts/
api_key yes (required) The Token Key from the PatchMon API credential
api_secret yes (required) The Token Secret from the PatchMon API credential
verify_ssl no true Whether to verify the PatchMon server's TLS certificate. Only disable on internal dev setups with self-signed certs

Using environment variables and Ansible Vault

Hard-coding the secret into patchmon_inventory.yml is not recommended. Use Ansible's environment-variable lookup or Ansible Vault instead:

---
plugin: patchmon.dynamic_inventory
api_url: https://patchmon.example.com/api/v1/api/hosts/
api_key: "{{ lookup('env', 'PATCHMON_API_KEY') }}"
api_secret: "{{ lookup('env', 'PATCHMON_API_SECRET') }}"
verify_ssl: true

Then:

export PATCHMON_API_KEY=your_token_key
export PATCHMON_API_SECRET=your_token_secret
ansible-inventory -i patchmon_inventory.yml --list

Making it the default inventory

Add to your ansible.cfg:

[defaults]
inventory = patchmon_inventory.yml

[inventory]
enable_plugins = patchmon.dynamic_inventory.dynamic_inventory

Every ansible / ansible-playbook / ansible-inventory invocation from this directory will now use PatchMon as its source of truth.


Usage

List all hosts

ansible-inventory -i patchmon_inventory.yml --list

Ping every host

ansible all -i patchmon_inventory.yml -m ping

Run a playbook against a PatchMon host group

If your PatchMon host group is named web_servers, the Ansible group name is also web_servers:

ansible-playbook -i patchmon_inventory.yml playbook.yml --limit web_servers

Intersect multiple groups

Standard Ansible group-pattern syntax applies. For example, to target all hosts in both web_servers and production:

ansible-playbook -i patchmon_inventory.yml playbook.yml --limit 'web_servers:&production'

API Response Format

The plugin expects the PatchMon API endpoint to return JSON shaped like this:

{
  "hosts": [
    {
      "hostname": "server1.example.com",
      "ip": "192.168.1.10",
      "host_groups": [
        { "name": "web_servers" },
        { "name": "production" }
      ]
    },
    {
      "hostname": "server2.example.com",
      "ip": "192.168.1.11",
      "host_groups": [
        { "name": "db_servers" },
        { "name": "production" }
      ]
    }
  ],
  "total": 2
}

This matches the shape returned by GET /api/v1/api/hosts in PatchMon 2.x (the host_groups array also contains an id field, which the plugin ignores).

Inventory mapping

  • Host name: hostname becomes the Ansible inventory key.
  • Connection IP: ip is set as the ansible_host variable on that host.
  • Groups: every { "name": "...", "id": "..." } in host_groups becomes an Ansible group, and the host is added to it.

Hosts with no entries in host_groups end up in Ansible's built-in ungrouped group.


Examples

List inventory output

ansible-inventory -i patchmon_inventory.yml --list

Example output:

{
  "_meta": {
    "hostvars": {
      "server1.example.com": { "ansible_host": "192.168.1.10" },
      "server2.example.com": { "ansible_host": "192.168.1.11" }
    }
  },
  "all": {
    "children": ["ungrouped", "web_servers", "db_servers", "production"]
  },
  "db_servers":   { "hosts": ["server2.example.com"] },
  "production":   { "hosts": ["server1.example.com", "server2.example.com"] },
  "web_servers":  { "hosts": ["server1.example.com"] }
}

Target specific groups

ansible-playbook -i patchmon_inventory.yml playbook.yml --limit web_servers
ansible-playbook -i patchmon_inventory.yml playbook.yml --limit production

Filtering at the API level

The scoped API /api/v1/api/hosts also accepts a ?hostgroup= query parameter. If you want a plugin invocation that only returns, say, the production group, set:

api_url: https://patchmon.example.com/api/v1/api/hosts/?hostgroup=production

This reduces the payload size and is handy when you have thousands of hosts and only want Ansible to see a subset.


Authentication and SSL

The plugin uses HTTP Basic Authentication. The Authorization header it sends is Basic base64(api_key:api_secret).

SSL certificate verification is on by default (verify_ssl: true). Disable it only when testing against an internal instance with a self-signed certificate, and never in production.


Troubleshooting

Test the API endpoint directly

curl -u "TOKEN_KEY:TOKEN_SECRET" https://patchmon.example.com/api/v1/api/hosts

You should get a JSON document with a hosts array. If not, double-check:

  • The URL. PatchMon 2.x exposes the endpoint under /api/v1/api/hosts (note the double /api/).
  • The credential. Ensure you're using the Token Key as the username and the Token Secret as the password, not a normal PatchMon user login.
  • That the credential has the host:read scope (or is unscoped).
  • That any IP allowlist on the credential includes the IP Ansible is calling from.

Debug the inventory

ansible-inventory -i patchmon_inventory.yml --list --debug
ansible-inventory -i patchmon_inventory.yml --list -vvv

Look for 401 Unauthorized (wrong credentials) or 403 Forbidden (missing scope / IP restriction) in the verbose output.

Common issues

Symptom Likely cause Fix
401 Unauthorized Token key or secret wrong Regenerate the credential in Settings → Integrations
403 Forbidden with "IP address not allowed" Allowlist on the credential blocks the controller Edit the credential and add the controller's public IP, or remove the allowlist
403 Forbidden with "Insufficient scope" Credential lacks host:read Edit the credential and tick the host:read scope
SSL cert error Self-signed cert, or verify_ssl: true against an internal PKI Install the CA chain on the controller, or temporarily set verify_ssl: false
Empty inventory No hosts in PatchMon, or ?hostgroup= filter matches nothing Test with curl first; verify the group name spelling
JSON parsing errors API URL points at the wrong path (e.g. /api/v1/hosts instead of /api/v1/api/hosts) Correct the URL. The scoped API is under /api/v1/api/

Security best practices

  • Create a dedicated credential for Ansible. Don't reuse the same API key across multiple tools. If one is compromised, you want to revoke just that one.
  • Scope it tightly. host:read is enough for inventory; grant no more.
  • IP-restrict the credential to your Ansible controller(s).
  • Set an expiration on the credential and rotate it as part of your normal key rotation.
  • Vault the secret. Use ansible-vault encrypt_string or an environment variable. Never commit plaintext secrets to git.
  • Always use HTTPS and verify_ssl: true in production.

Contributing

Pull requests are welcome on PatchMon-ansible. Issues and feature requests can be filed at PatchMon-ansible/issues.