How to retrieve root partition disk size in GB with Ansible playbook tasks

Written by - 0 comments

Published on - Listed in Ansible Linux


Within an Ansible playbook I wanted to retrieve the root partitions size and use this as a variable for a later task. What seemed to be an easy task, turned out to be a bigger head-scratcher than I thought. But let's start at the beginning.

Ansible

Gathering facts

When gathering facts with Ansible, the "ansible_mounts" array contains the mounted file systems of the target machine:

ck@ansible:/pub/ansible$ ansible -m setup target
[...]
        "ansible_mounts": [
            {
                "block_available": 1211001,
                "block_size": 4096,
                "block_total": 2554693,
                "block_used": 1343692,
                "device": "/dev/vgdata/target",
                "fstype": "ext4",
                "inode_available": 551110,
                "inode_total": 655360,
                "inode_used": 104250,
                "mount": "/",
                "options": "rw,relatime",
                "size_available": 4960260096,
                "size_total": 10464022528,
                "uuid": "N/A"
            }
        ],
[...]

The "size_total" represents the total size of the file system, which can also be seen and verified with df on the target machine:

root@target:~# df -B1 /
Filesystem         Type   1B-blocks       Used  Available Use% Mounted on
/dev/vgdata/target ext4 10464022528 4964831232 4945543168  51% /

Using json_query to retrieve root partition size

As "ansible_mounts" is an array which may contain multiple file systems, I needed to make sure to only select the root partition, mounted on /, of the target machine. This can be done by using the json_query filter:

- name: FACT - Retrieve root disk size
  set_fact:
    root_disk_size: "{{ ansible_mounts|json_query('[?mount == `/`].size_total') }}"

This task sets a fact (variable) named "root_disk_size". The value is retrieved from the Ansible facts "ansible_mounts". The json_query function searches for an entry where the "mount" key has a value of "/". From this entry, retrieve the "size_total" value.

However the first run failed with the following error:

TASK [FACT - Retrieve root disk size] *******************************************************
fatal: [target]: FAILED! => {"msg": "You need to install \"jmespath\" prior to running json_query filter"}

The Ansible server, on which the playbook runs, requires the jmespath Python module:

ck@ansible:/pub/ansible$ sudo apt-get install python3-jmespath

After this package was installed, the task could be executed.

Arithmetic struggles with type errors

Now with the disk size (in Bytes) at hand, I wanted to calculate the disk size in GB. The basic formula is:

root_disk_size / 1024^3

Spoiler alert: ^3 doesn't work in Ansible, need to use division by 1024 three times

Unsupported operand types list and int

I thought I could simply create a new fact/variable with the calculation:

- name: FACT - Retrieve root disk size
  set_fact:
    root_disk_size: "{{ ansible_mounts|json_query('[?mount == `/`].size_total') }}"

- name: FACT - Calculate disk size in GB
  set_fact:
    root_disk_size_gb: "{{ root_disk_size / 1024 / 1024 / 1024 }}"

But this task ran into an error:

fatal: [target]: FAILED! => {"msg": "An unhandled exception occurred while templating '{{ root_disk_size / 1024 / 1024 / 1024 }}'. Error was a <class 'ansible.errors.AnsibleError'>, original message: Unexpected templating type error occurred on ({{ root_disk_size / 1024 / 1024 / 1024 }}): unsupported operand type(s) for /: 'list' and 'int'"}

A debug task before the actual calculation helps to understand why:

- name: DEBUG
  debug:
    msg: "root disk size is: {{ root_disk_size }}"
  vars:
    root_disk_size: "{{ ansible_mounts|json_query('[?mount == `/`].size_total') }}"

The output shows the value of the variable "root_disk_size":

TASK [DEBUG] ***************************************************************************
ok: [target] => {
    "msg": "root disk size is: [10464022528]"
}

I expected a number of bytes showing up in the output, yet the number is shown up inside square brackets []. This is because the root_disk_size variable is a "list" array, containing potentially further values (if the json_query filter would be different).

As the json_query filter is set up to only match one particular partition (the "/" mount), I am fairly sure there is only one result. Hence the json_query can be adjusted to only show a single (the first) result, by appending [0]:

 - name: DEBUG
  debug:
    msg: "{{ root_disk_size }}"
  vars:
    root_disk_size: "{{ ansible_mounts|json_query('[?mount == `/`].size_total|[0]') }}"

The output now shows the value without the square brackets:

TASK [DEBUG] **************************************************************************
ok: [target] => {
    "msg": "root disk size is: 10464022528"
}

Unsupported operand types AnsibleUnsafeText and int

With this I thought the problem is solved any my GB calculation would work. But nope. I ran into yet another error:

TASK [DEBUG] **************************************************************************
ok: [target] => {
    "msg": "10464022528"
}

TASK [FACT - Calculate disk size in GB] ***********************************************
fatal: [target]: FAILED! => {"msg": "Unexpected templating type error occurred on ({{ root_disk_size / 1024 * 3 }}): unsupported operand type(s) for /: 'AnsibleUnsafeText' and 'int'"}

I kind of expected this one. By default, all (templating) variables in Ansible are a "string" (unless otherwise defined). Hence how do you want to run a mathematical operation on a string? 

The solution is to use the root_disk_size variable and set it as integer (int) before we want to do a calculation:

- name: DEBUG
  debug:
    msg: "{{ root_disk_size }}"
  vars:
    root_disk_size: "{{ ansible_mounts|json_query('[?mount == `/`].size_total|[0]') }}"

# Set facts
- name: FACT - Retrieve root disk size
  set_fact:
    root_disk_size: "{{ ansible_mounts|json_query('[?mount == `/`].size_total|[0]') }}"

- name: FACT - Calculate disk size in GB
  #vars:
  #  root_disk_size: "{{ ansible_mounts|json_query('[?mount == `/`].size_total|[0]') }}"
  set_fact:
    root_disk_size_gb: '{{ root_disk_size|int / 1024 / 1024 / 1024 }}'

- name: FACT Debug root_disk_size
  debug:
    msg: "{{ root_disk_size }}"
  ignore_errors: True

- name: FACT Debug root_disk_size_gb
  debug:
    msg: "{{ root_disk_size_gb }}"
  ignore_errors: True

The output of the playbook run now shows:

TASK [DEBUG] *****************************************************************************
ok: [target] => {
    "msg": "10464022528"
}

TASK [FACT - Retrieve root disk size] ****************************************************
ok: [target]

TASK [FACT - Calculate disk size in GB] **************************************************
ok: [target]

TASK [FACT Debug root_disk_size] *********************************************************
ok: [target] => {
    "msg": "10464022528"
}

TASK [FACT Debug root_disk_size_gb] ******************************************************
ok: [target] => {
    "msg": "9.745380401611328"
}

Finally the calculation has worked and we get the disk size in GB, stored inside the "root_disk_size_gb" variable. However the GB output is fairly long...

Round the GB size

By using the integrated Jinja2 round() function, this should be do-able:

- name: FACT - Calculate disk size in GB
  #vars:
  #  root_disk_size: "{{ ansible_mounts|json_query('[?mount == `/`].size_total|[0]') }}"
  set_fact:
    root_disk_size_gb: '{{ root_disk_size|int / 1024 / 1024 / 1024|round }}'

- name: FACT Debug root_disk_size_gb
  debug:
    msg: "{{ root_disk_size_gb }}"
  ignore_errors: True

But this did not have any effect. The value of "root_disk_size_gb" still showed a large number with lots of decimals:

TASK [FACT - Calculate disk size in GB] *************************************************
ok: [target]

TASK [FACT Debug root_disk_size_gb] *****************************************************
ok: [target] => {
    "msg": "9.745380401611328"
}

The mathematical operation needs to be placed into parantheses before using round(), as explained in this StackOverflow answer

Final playbook to retrieve the root partition size in GB

And with this last piece of fixing, the following final Ansible playbook works:

- name: DEBUG
  debug:
    msg: "{{ root_disk_size }}"
  vars:
    root_disk_size: "{{ ansible_mounts|json_query('[?mount == `/`].size_total|[0]') }}"

# Set facts
- name: FACT - Retrieve root disk size
  set_fact:
    root_disk_size: "{{ ansible_mounts|json_query('[?mount == `/`].size_total|[0]') }}"


- name: FACT - Calculate disk size in GB
  #vars:
  #  root_disk_size: "{{ ansible_mounts|json_query('[?mount == `/`].size_total|[0]') }}"
  set_fact:
    root_disk_size_gb: '{{ (root_disk_size|int / 1024 / 1024 / 1024)|round }}'


- name: FACT Debug root_disk_size
  debug:
    msg: "{{ root_disk_size }}"
  ignore_errors: True

- name: FACT Debug root_disk_size_gb
  debug:
    msg: "{{ root_disk_size_gb }}"
  ignore_errors: True

Playbook output:

TASK [DEBUG] ***************************************************************************
ok: [target] => {
    "msg": "10464022528"
}

TASK [FACT - Retrieve root disk size] **************************************************
ok: [target]

TASK [FACT - Calculate disk size in GB] ************************************************
ok: [target]

TASK [FACT Debug root_disk_size] *******************************************************
ok: [target] => {
    "msg": "10464022528"
}

TASK [FACT Debug root_disk_size_gb] ****************************************************
ok: [target] => {
    "msg": "10.0"
}

Hurray, this looks good!

What is this for?

At the end this information is retrieved to automatically add the target machine into Netbox, using the Netbox collection for Ansible. If I get to it, maybe I'll post this in another article.


Add a comment

Show form to leave a comment

Comments (newest first)

No comments yet.

RSS feed

Blog Tags:

  AWS   Android   Ansible   Apache   Apple   Atlassian   BSD   Backup   Bash   Bluecoat   CMS   Chef   Cloud   Coding   Consul   Containers   CouchDB   DB   DNS   Database   Databases   Docker   ELK   Elasticsearch   Filebeat   FreeBSD   Galera   Git   GlusterFS   Grafana   Graphics   HAProxy   HTML   Hacks   Hardware   Icinga   Influx   Internet   Java   KVM   Kibana   Kodi   Kubernetes   LVM   LXC   Linux   Logstash   Mac   Macintosh   Mail   MariaDB   Minio   MongoDB   Monitoring   Multimedia   MySQL   NFS   Nagios   Network   Nginx   OSSEC   OTRS   Office   OpenSearch   PGSQL   PHP   Perl   Personal   PostgreSQL   Postgres   PowerDNS   Proxmox   Proxy   Python   Rancher   Rant   Redis   Roundcube   SSL   Samba   Seafile   Security   Shell   SmartOS   Solaris   Surveillance   Systemd   TLS   Tomcat   Ubuntu   Unix   VMWare   VMware   Varnish   Virtualization   Windows   Wireless   Wordpress   Wyse   ZFS   Zoneminder