ansible-cmdb - Structuring and Visualizing Ansible Facts
Greetings!

ansible-cmdb is a Python program that structures collected Ansible facts and visualizes them using beautifully formatted HTML.

Ansible facts are data about servers (Ansible hosts) collected using the setup module. They are “facts” about the system, for example data about the OS, network interfaces, block devices, and other system components.

With ansible-cmdb, you can also export structured data to files in the following formats: json, csv, sql, txt_table. Such files are convenient to use for analysis, storage, or further processing. But that is a topic for a separate article😉.

In this note, I will explain📋:

It is assumed that you know what Ansible is and know how to use it🙄. If not, I recommend reading my articles:

All examples from the article were performed in the environment of the distribution Linux Mint Debian Edition 6 🍃 (Debian 12). In all other distributions, the process looks more or less similar.

Installing ansible-cmdb

First, go to the Ansible working directory where your inventory and configuration files are located (for me this is ~/ansible):

BASH
cd ~/ansible/
Click to expand and view more

As I said earlier, ansible-cmdb, like Ansible itself, is written in the Python programming language🐍. Therefore, it is recommended to create a venv virtual environment 📦 and install ansible-cmdb inside it:

Method #1 - venv, if your Python version is below 3.12

Run in the terminal:

BASH
mkdir -p ./cmdb/venv

python3 -m venv ./cmdb/venv/ansible-cmdb

./cmdb/venv/ansible-cmdb/bin/pip install ansible-cmdb
Click to expand and view more

Method #2 - docker, if your Python version is 3.12 and higher

Unfortunately, ansible-cmdb has not been updated for quite a while, and in Python 3.12 the imp library became deprecated. In this case, to run ansible-cmdb, you need to install an earlier Python version.

A universal way to do this is Docker:

BASH
mkdir -p ./cmdb/venv

docker run -u $(id -u):$(id -g) -v ./cmdb:/cmdb python:3.11-alpine python -m venv /cmdb/venv/ansible-cmdb

docker run -u $(id -u):$(id -g) -v ./cmdb:/cmdb python:3.11-alpine /cmdb/venv/ansible-cmdb/bin/pip install ansible-cmdb
Click to expand and view more

At the end of installation, there should be a message like this:

PLAINTEXT
Installing collected packages: ushlex, jsonxs, pyyaml, MarkupSafe, mako, ansible-cmdb
Successfully installed MarkupSafe-3.0.2 ansible-cmdb-1.31 jsonxs-0.6 mako-1.3.10 pyyaml-6.0.2 ushlex-0.99.1
Click to expand and view more

To avoid activating venv every time in the future (or entering docker) to work with ansible-cmdb, create a small script.

If you installed through venv (Method #1):

BASH
cat << EOF > "${PWD}"/cmdb/ansible_cmdb.sh
#!/bin/bash
"${PWD}/cmdb/venv/ansible-cmdb/bin/python" "${PWD}/cmdb/venv/ansible-cmdb/lib/ansiblecmdb/ansible-cmdb.py" "\$@"
EOF
Click to expand and view more

If you installed in Docker (Method #2):

BASH
cat << EOF > "${PWD}"/cmdb/ansible_cmdb.sh
#!/bin/bash
docker run -v "${PWD}/cmdb":"/cmdb" python:3.11-alpine /cmdb/venv/ansible-cmdb/bin/python /cmdb/venv/ansible-cmdb/lib/ansiblecmdb/ansible-cmdb.py "\$@"
EOF
Click to expand and view more

Make it executable and add a symbolic link to it in the ~/.local/bin directory for convenience:

BASH
chmod +x ./cmdb/ansible_cmdb.sh

ln -s "${PWD}"/cmdb/ansible_cmdb.sh ~/.local/bin/ansible-cmdb
Click to expand and view more

Check:

BASH
ansible-cmdb --help
Click to expand and view more
PLAINTEXT
Usage: ansible-cmdb.py [option] <dir> > output.html

Options:
  --version             show program's version number and exit
  -h, --help            show this help message and exit
  -t TEMPLATE, --template=TEMPLATE
                        Template to use. Default is 'html_fancy'
  -i INVENTORY, --inventory=INVENTORY
                        Inventory to read extra info from
  -f, --fact-cache      <dir> contains fact-cache files
  -p PARAMS, --params=PARAMS
                        Params to send to template
  -d, --debug           Show debug output
  -q, --quiet           Don't report warnings
  -c COLUMNS, --columns=COLUMNS
                        Show only given columns
  -C CUST_COLS, --cust-cols=CUST_COLS
                        Path to a custom columns definition file
  -l LIMIT, --limit=LIMIT
                        Limit hosts to pattern
  --exclude-cols=EXCLUDE_COLUMNS
                        Exclude cols from output
Click to expand and view more

ansible-cmdb Usage Example

Create a directory for basic facts and start collecting them using ansible and the setup module:

BASH
mkdir -p ./cmdb/facts/basic

ansible -m setup --tree ./cmdb/facts/basic all &> /dev/null
Click to expand and view more

Now use ansible-cmdb to generate an HTML file based on the collected data:

BASH
ansible-cmdb -p collapsed=1 ./cmdb/facts/basic > ./cmdb/cmdb.html
Click to expand and view more

Now simply open the resulting file in a browser:

Neat and convenient. The page has interactive elements: search, filtering, and so on. Very convenient😏.

For generating json | csv | sql | txt_table | markdown, the command will be approximately like this:

BASH
ansible-cmdb -t csv ./cmdb/facts/basic > ./cmdb/cmdb.csv
Click to expand and view more
PLAINTEXT
"Name","OS","IP","Arch","Mem","MemFree","MemUsed","CPUs","Virt","Disk avail"
"host1","Debian 12.7","10.1.1.10","x86_64/x86_64","8g","0g","1g","4","kvm/guest","8.3g, 0.2g"
"host2","Debian 12.10","0.1.1.11","x86_64/x86_64","8g","2g","2g",6","kvm/guest","7.8g, 0.3g"
"host3","Debian 12.8","0.1.1.12","x86_64/x86_64","8g","0g","3g","4","kvm/guest","1.9g"
"host4","Debian 12.6","0.1.1.13","x86_64/x86_64","8g","0g","1g","8","kvm/guest","8.7g, 0.2g"
"host5","Debian 12.7","0.1.1.14","x86_64/x86_64","8g","0g","1g","2","kvm/guest","9.0g, 0.2g"
"host6","Debian 12.6","0.1.1.15","x86_64/x86_64","8g","0g","1g","2","kvm/guest","0.8g, 3.7g, 3.7g, 0.4g"
Click to expand and view more

It is worth warning that the full list of facts is available only in the JSON template. Other formats have a limited number of fields defined in templates located at: ./cmdb/venv/ansible-cmdb/lib/ansiblecmdb/data/tpl. If necessary, they can be customized to your needs.

Adding Custom Facts

ansible-cmdb provides flexible customization capabilities for the final file👨🎨.

I will show this using the example of adding my own fact as a list of host routes, which Ansible does not collect by default🤷♂️.

My implementation is done using a Bash script that outputs the host route list in JSON format📃.

Let’s begin👨🏭. Create the script file:

BASH
nvim ./cmdb/routes_to_json.sh
Click to expand and view more

Fill it with this content:

BASH
#!/bin/bash

# Get routes into an array
mapfile -t routes < <(ip r)

# JSON start
RESULT='{"custom_facts": {"IP Routes": ['

# Add routes
for i in "${!routes[@]}"; do
    # Escape special characters
    line="${routes[$i]//\\/\\\\}"
    line="${line//\"/\\\"}"
    
    # Add route as a separate object
    RESULT+="{\"#$((i+1))\": \"$line\"}"
    
    # Add a comma between elements (except the last one)
    [ $i -lt $((${#routes[@]} - 1)) ] && RESULT+=","
done

# Finish JSON
RESULT+=']}}'

# Print result
echo "$RESULT"
Click to expand and view more

Make it executable:

BASH
chmod +x ./cmdb/routes_to_json.sh
Click to expand and view more

Check:

BASH
./cmdb/routes_to_json.sh | python3 -m json.tool
Click to expand and view more

The output should be approximately like this:

JSON
{
    "custom_facts": {
        "IP Routes": [
            {
                "#1": "default dev vpn0 proto static scope link metric 50 "
            },
            {
                "#2": "default via 10.10.10.1 dev eth0 proto dhcp src 10.10.10.30 metric 600 "
            },
            {
                "#3": "10.11.11.0/24 dev vpn0 proto kernel scope link src 10.11.11.15 metric 50 "
            }
        ]
    }
}
Click to expand and view more

Now create a new playbook that will collect our custom fact (running the script above on remote hosts and collecting the output into local files):

BASH
nvim ./cmdb/get_custom_facts.yml
Click to expand and view more

Fill it:

BASH
---

- name: Get custom facts
  hosts: all
  gather_facts: false

  tasks:

    - name: Run script to get info about ip routes
      script: "~/ansible/cmdb/routes_to_json.sh"
      register: script_output

    - name: Create local output files with ip routes info
      local_action: copy content="{{ script_output.stdout }}" dest="~/ansible/cmdb/facts/custom/{{ inventory_hostname }}"
Click to expand and view more

Now create a separate directory for custom facts, collect them with ansible-playbook, and generate the updated HTML using ansible-cmdb:

BASH
mkdir -p ./cmdb/facts/custom

ansible-playbook ./cmdb/get_custom_facts.yml

ansible-cmdb -p collapsed=1 ./cmdb/facts/{basic,custom} > ./cmdb/cmdb.html
Click to expand and view more

The final HTML will have a new “Custom facts” section. In my opinion, it looks pretty good😌:

ansible-cmdb allows you to configure custom columns in HTML and much more. I recommend studying the documentation in more detail📚 (links at the end of the article).

Access to cmdb.html by URL in Nginx

If you need to provide this information to someone else, you can add the final HTML as a separate page on your web server, for example Nginx 🌐.

If the web server runs as a user with limited permissions, the easiest option is to create a hardlink to your file in the Nginx site root (conditionally the root is /var/www/html:

BASH
sudo ln "${PWD}"/cmdb/cmdb.html /var/www/html/
Click to expand and view more

And in the Nginx config, add for example this location:

BASH
server {
    listen 80;
    server_name localhost;

    root /var/www/html;
    index index.html index.htm;

    location / {
        try_files $uri $uri.html $uri/ =404;
    }
}
Click to expand and view more

Something like this:

Automating Fact Collection/Updates

There is no way around automation in the world of administration🙂↔️. Let’s write a script for collecting/updating facts and configure its daily launch at 01:00 by a systemd timer in our user’s space (--user).

Create the script file:

BASH
nvim ./cmdb/update_cmdb.sh
Click to expand and view more

Fill it:

BASH
#!/usr/bin/env bash

set -Eeuo pipefail

export PATH="$PATH:$HOME/.local/bin"

WORK_DIR="$HOME/ansible/"

echo "Script started"

cd "$WORK_DIR"

echo "Get basic facts"
ansible -m setup --tree ./cmdb/facts/basic all &> /dev/null || true

echo "Get custom facts"
ansible-playbook ./cmdb/get_custom_facts.yml &> /dev/null || true

echo "Generate cmdb html file"
ansible-cmdb -p collapsed=1 ./cmdb/facts/{basic,custom} > ./cmdb/cmdb.html

echo "Script done"
echo "--------------------------------------"
Click to expand and view more

Save it, make the file executable, create a link in the ~/.local/bin directory for convenience, and check:

BASH
chmod +x ./cmdb/update_cmdb.sh

ln -s "${PWD}"/cmdb/update_cmdb.sh ~/.local/bin/update-cmdb

update-cmdb

# modification time
stat -c '%y' ./cmdb/cmdb.html
Click to expand and view more

Next, create a systemd service unit:

BASH
systemctl --user edit --full --force update-cmdb.service
Click to expand and view more

Fill it:

BASH
[Unit]
Description=Update ansible cmdb

[Service]
WorkingDirectory=%h/ansible
ExecStart=%h/.local/bin/update-cmdb
Click to expand and view more

Now create a timer unit:

BASH
systemctl --user edit --full --force update-cmdb.timer
Click to expand and view more

Fill it:

BASH
[Unit]
Description=Update ansible cmdb daily at 01:00

[Timer]
OnCalendar=*-*-* 01:00:00
Persistent=true

[Install]
WantedBy=timers.target
Click to expand and view more

Activate the timer:

BASH
systemctl --user enable --now update-cmdb.timer
Click to expand and view more

Check:

BASH
systemctl --user status update-cmdb.timer

systemctl --user status update-cmdb.service

journalctl --user -fu update-cmdb
Click to expand and view more

Now our HTML file will always contain up-to-date data about Ansible hosts👍.

Materials Used

Copyright Notice

Author: Ivan Cherniy

Link: https://r4ven.me/en/automation/ansible-cmdb-strukturizacziya-i-vizualizacziya-ansible-facts/

License: CC BY-NC-SA 4.0

Blog materials may be used with attribution to the author and source, for non-commercial purposes, and under the same license.

Start searching

Enter keywords to search articles

↑↓
ESC
⌘K Shortcut