Resource reporting
Ansible modules often interact with external systems such as clouds, hypervisors, and network controllers to manage resources that are not declared in your inventory. Resource reporting gives you structured visibility into what your automation actually touches, even for resources that never appear in the inventory.
You can add resource reporting to your Ansible collection by creating a lightweight YAML query file that maps module return values to a standardized resource taxonomy.
Each entry uses a jq expression to extract resource metadata from module output.
How resource reporting works
Resource reporting uses a query file inside your collection to describe the resources that your modules manage. When a module runs, the query file tells any consuming tool how to extract three pieces of information from the module return values:
A human-readable resource name.
Canonical facts that uniquely identify the resource for deduplication.
Metadata that categorizes the resource using a standardized taxonomy.
The result is machine-readable documentation about what your modules manage. For collection developers, resource reporting is like having excellent return value documentation that tools can consume automatically. For users, it creates a consistent, unified picture of automation activity across different vendors and platforms.
Note
The query file format, resource taxonomy, and jq expression pattern are an open schema.
Any automation platform, reporting tool, or custom script can consume the query file to extract structured resource data from module output.
The normalized resource taxonomy
The normalized resource taxonomy maps vendor-specific resource types to standard names.
For example, a VMware VM and an AWS EC2 instance are both type virtual_machine.
An Azure load balancer and an F5 BIG-IP VIP are both type load_balancer.
The taxonomy organizes resources into categories and device types.
When you write a query, set the facts.device_type field to the snake_case value from the tables below.
Compute
Resource |
|
|---|---|
Virtual machines |
|
Containers (managed) |
|
Hypervisors |
|
Bare metal |
|
Serverless functions |
|
Auto scaling groups |
|
Networking
Resource |
|
|---|---|
Switches |
|
Routers |
|
Firewalls |
|
Load balancers |
|
Virtual private clouds |
|
Subnets |
|
VPNs |
|
Gateways |
|
DNS services |
|
Wireless access points |
|
SD-WAN |
|
Storage
Resource |
|
|---|---|
Object storage |
|
Block storage |
|
File storage |
|
Archive storage |
|
Database
Resource |
|
|---|---|
Relational (SQL) |
|
NoSQL |
|
Data warehouse |
|
In-memory or cache |
|
DevOps and app integration
Resource |
|
|---|---|
CI/CD platforms |
|
Container registries |
|
Message queues |
|
API endpoints |
|
Note
If your resource does not fit one of these standard types, open a topic on the Ansible forum to propose a new device type.
Adding resource reporting to a collection
To add resource reporting to your collection, create a query file and write jq expressions that extract resource metadata from your module return values.
Creating the query file
Create the file extensions/audit/event_query.yml in your collection root directory.
This is the standard location for embedded query files that ship with your collection.
my_namespace/my_collection/
├── extensions/
│ └── audit/
│ └── event_query.yml
├── plugins/
├── meta/
└── galaxy.yml
Writing the query
The query file maps each module to a jq expression using the Fully Qualified Collection Name (FQCN) as the key.
The jq expression runs against the module return values and must output a JSON object with the following fields:
Field |
Requirement |
Description |
|---|---|---|
|
Required |
A human-readable display name for the resource, for example a VM name or switch hostname. |
|
Required |
A dictionary of facts that uniquely identify the resource and deduplicate it across job runs. |
|
Optional |
Metadata for categorization. Must include |
The following example shows a complete query file entry:
---
# extensions/audit/event_query.yml
community.vmware.vmware_guest:
query: >-
{
name: .instance.hw_name,
canonical_facts: {
host_name: .instance.hw_name,
uuid: .instance.hw_product_uuid
},
facts: {
device_type: "virtual_machine",
guest_id: .instance.hw_guest_id
}
}
Choosing canonical facts
Select fields that are globally unique and stable for the life of the resource.
Recommended: UUIDs, serial numbers, permanent MAC addresses.
Conditional: Hostnames (if unique in the environment), IP addresses (static only).
Avoid: Dynamic IPs, random session IDs.
{
"canonical_facts": {
"uuid": "550e8400-e29b-41d4-a716-446655440000",
"serial_number": "XYZ-123"
}
}
Setting the device type
Map your resource to a standard device_type value from the normalized resource taxonomy.
You can optionally include a platform field to indicate the underlying platform, for example aws, vmware, or azure.
{
"facts": {
"device_type": "virtual_machine",
"platform": "vmware",
"guest_id": "rhel8_64"
}
}
Platform-specific examples
Different platforms require different querying strategies based on how their APIs return data.
VMware (flat structure)
VMware modules return a flat structure where the top-level key defines the node type. Map directly to the returned keys.
---
# extensions/audit/event_query.yml
community.vmware.vmware_guest:
query: >-
{
name: .instance.hw_name,
canonical_facts: {
host_name: .instance.hw_name,
uuid: .instance.hw_product_uuid
},
facts: {
device_type: "virtual_machine",
guest_id: .instance.hw_guest_id
}
}
Azure (hierarchical structure)
Azure resources are hierarchical and use verbose resource IDs.
Because the full Azure resource ID contains the resource type, you can use a jq regex to dynamically capture and categorize it.
azure.azcollection.azure_rm_virtualmachine:
query: >-
{
name: .name,
canonical_facts: {
id: .id
},
facts: {
device_type: "virtual_machine",
azure_type: ((.id | capture("/providers/[Mm]icrosoft.(?<resourcetype>[^/]+)/")? |
.resourcetype) | ascii_downcase)
}
}
In this example, Microsoft.Compute in the resource ID is captured and lowercased to "compute".
AWS (implied types)
AWS info modules often return a list of resources, so your jq expression must iterate over them.
The resource type is implied by the module you query rather than included in the resource ID.
amazon.aws.ec2_instance_info:
query: >-
.instances[] | {
name: (.tags.Name // .instance_id),
canonical_facts: {
instance_id: .instance_id
},
facts: {
device_type: "virtual_machine",
status: .state.name
}
}
To manage the mapping from AWS module to resource type, define a mapping dictionary in your expression:
{
"ec2_instance": "virtual_machine",
"rds_instance": "database_relational",
"ec2_vpc_net": "vpc"
}
Module matching rules
The query file uses module FQCNs as keys. You can target modules with exact matches or wildcard patterns.
Exact match
Target a specific module.
cisco.ios.ios_facts:
query: >-
...
Wildcard match
Target all modules in a collection. Use this carefully because different modules return different data structures.
community.vmware.*:
query: >-
...
Testing your query
Before publishing your collection, verify your jq expression against real module output.
Run a playbook with your module and capture the JSON output. Register the return value or run the module with
-vvvto see the full output.- name: Gather VM info community.vmware.vmware_guest: name: my-vm register: result
Save the module output to a file and test your
jqexpression from the command line.cat module_output.json | jq '{ name: .instance.hw_name, canonical_facts: { uuid: .instance.hw_uuid }, facts: { device_type: "virtual_machine" } }'
Validate that the output JSON contains a valid
namestring and non-emptycanonical_facts.
See also
- Developing collections
Learn how to develop Ansible collections
- Collection structure
Directories and files included in a collection
- Ansible forum
Got questions? Need help? Want to share your ideas? Visit the Ansible forum