Chapter 3 of 6
Ansible Dynamic Inventory
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.
- GitHub repository: github.com/PatchMon/PatchMon-ansible
- Ansible Galaxy namespace:
patchmon.dynamic_inventory - License: AGPL-3.0-or-later
Related pages:
- Integration API Documentation: full reference for the scoped
/api/v1/api/...endpoints the plugin talks to- Auto-Enrolment API Docs: how to create the Basic-Auth credentials this plugin needs
What the plugin does
For each request, the plugin:
- Calls
GET /api/v1/api/hostson your PatchMon instance with HTTP Basic Auth. - Receives a JSON list of active hosts, their IPs, and their PatchMon host-group memberships.
- Builds an Ansible inventory in memory:
- Each PatchMon host becomes an Ansible host, keyed by
hostname. ansible_hostis set to the host'sipfield (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.
- Each PatchMon host becomes an Ansible host, keyed by
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
From Ansible Galaxy (recommended)
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.
- Sign in to PatchMon as a user with
can_manage_settings. - Go to Settings → Integrations and select the API tab.
- 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.
- Name: e.g.
- Click Create.
- 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_keyconfig value is PatchMon's Token Key. The plugin'sapi_secretis 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:
hostnamebecomes the Ansible inventory key. - Connection IP:
ipis set as theansible_hostvariable on that host. - Groups: every
{ "name": "...", "id": "..." }inhost_groupsbecomes 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:readscope (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:readis 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_stringor an environment variable. Never commit plaintext secrets to git. - Always use HTTPS and
verify_ssl: truein production.
Contributing
Pull requests are welcome on PatchMon-ansible. Issues and feature requests can be filed at PatchMon-ansible/issues.