Harness PuppetDB data through API queries for facts, reports, and resources, then consume it from external tools like Grafana. Also learn exported resource patterns for inventory, monitoring targets, and cross-node network configuration.

In episode 16 you learned to extend Puppet with defined types, custom facts, functions, and custom providers. Now it's time to harness the wealth of data Puppet collects on every run. Every fact, report, and resource reported by agents is stored in PuppetDB and can be queried through the API.
In episode 9 we already got a basic introduction to PuppetDB and exported resources. In this episode we'll go deeper into API queries: filtering facts, reading reports, and pulling resource data. We'll also explore advanced exported resource patterns for automatic inventory, monitoring targets, and interdependent cross-node network configuration.
In this episode we'll cover the PuppetDB query API endpoints, example fact, report, and resource queries, consuming data from external tools, and exported resource patterns for various cross-node needs.
PuppetDB provides a REST API accessible over HTTP on the default port 8080 for open source, or via the PE console. The main endpoint is at /pdb/query/v4 with a query language that resembles JSONPath.
The simplest query shows all nodes PuppetDB knows about:
curl -s 'http://localhost:8080/pdb/query/v4/nodes'The result is a JSON array, for example:
[
{
"certname": "web01.example.com",
"catalog_timestamp": "2026-08-03T01:00:00.000Z",
"facts_timestamp": "2026-08-03T01:00:00.000Z",
"report_timestamp": "2026-08-03T01:05:00.000Z"
}
]For Puppet Enterprise users, a more convenient CLI is available:
puppet query 'nodes[certname] {}'Tip
Use the limit and offset parameters for large queries, for example ?limit=100&offset=0. Get into the habit of adding &limit=1 when exploring so responses stay light and easy to read.
The facts endpoint /pdb/query/v4/facts lets us look up a specific fact from all nodes or from a single node:
curl -s 'http://localhost:8080/pdb/query/v4/facts/web01.example.com/osfamily'To see a node's latest run report, we query the /pdb/query/v4/reports endpoint:
curl -s 'http://localhost:8080/pdb/query/v4/reports?query=%5B%22%3D%22%2C%22certname%22%2C%22web01.example.com%22%5D&limit=1'With the JSON query language, the expression ["=", "certname", "web01.example.com"] is written URL-encoded. PuppetDB also provides a resources endpoint for tracing a specific resource across all catalogs:
curl -s 'http://localhost:8080/pdb/query/v4/resources/Service/sshd'This query is useful for auditing: for example, making sure all nodes use the same configuration, or finding nodes that violate policy.
PuppetDB data isn't only used by manifests — it can also be consumed by external tools. Dashboards like Grafana can visualize query results through an HTTP datasource plugin or a small script that calls the PuppetDB API.
curl -s 'http://localhost:8080/pdb/query/v4/facts/osfamily' \
| jq 'group_by(.value) | map({os: .[0].value, count: length})'Note
Some external tools, like a CMDB inventory or security tools, take periodic snapshots from PuppetDB via the API. Make sure access to the PuppetDB port is network-restricted, because fact data can contain sensitive information like internal hostnames and software versions.
Exported resources let one node publish resources, and another node collects them. The classic pattern is building an SSH host inventory: every node exports its host key, then one collector node collects them all.
@@sshkey { "sshkey-${facts['networking']['fqdn']}":
ensure => present,
name => $facts['networking']['fqdn'],
key => $facts['ssh']['rsa']['key'],
type => 'rsa',
tag => 'sshkeys',
}Sshkey <<| tag == 'sshkeys' |>>Warning
When a node is removed from PuppetDB, its exported resources disappear too, and the collect automatically removes entries that are no longer published. This keeps the inventory in sync with actually active nodes, as long as old nodes are purged from PuppetDB with puppet node purge web01.example.com.
The same pattern applies to monitoring configuration. Every node exports a monitoring target definition, and the monitoring server collects those definitions into a complete configuration without needing a manually maintained node list.
@@nagios_host { $facts['networking']['fqdn']:
ensure => present,
address => $facts['networking']['ip'],
use => 'generic-host',
tag => 'monitoring',
}
Nagios_host <<| tag == 'monitoring' |>>When a new node is added, it automatically exports itself and the monitoring server adds the new target on the next run. No hand-edited host list, and no forgotten nodes.
The hardest case is interdependent cross-node configuration, for example a load balancer that must know the addresses of all its backends. Backend nodes export their backend vhost definition, and the load balancer node collects them to build the upstream configuration.
@@nginx::resource::upstream { "backend-${facts['networking']['fqdn']}":
members => ["${facts['networking']['ip']}:8080"],
tag => "lb-${trusted['certname']}",
}Nginx::Resource::Upstream <<| tag == "lb-${trusted['certname']}" |>>The trusted-based certname tag ensures only nodes that are actually backends of a specific load balancer get collected. This is an example of combining exported resources with trusted facts data — something a static config can't do.
Important
Exported resources require an active PuppetDB because exported resources are stored and resolved by PuppetDB at collect time. Make sure storeconfigs points to PuppetDB, not to default processing without a store. Without PuppetDB, <<| |>> won't collect anything.
In this episode 17 you understood how to mine PuppetDB data through API queries for facts, reports, and resources, consume the results from external tools like Grafana, and apply advanced exported resource patterns for host inventory, monitoring targets, and cross-node network configuration.
Key takeaways:
/pdb/query/v4 provides facts, reports, and resource data in a structured form.puppet query CLI makes things easier for PE users.@@ and <<| |>>.The bigger the infrastructure, the bigger the risk of breaking changes. That's why testing becomes a necessity, not an option. In episode 18 we'll cover Learn Puppet - Testing: unit tests with rspec-puppet, fixtures with puppetlabs_spec_helper, and acceptance tests using Beaker in CI.