1

I'm struggling to get a solution for this:

I need to create a CSV file with all the filesystems and respective mountpoints on every server, using Ansible.

I managed to create a Python script that displays the information I want:

hostname,filesystem,mountpoint

Works beautifully on every server EXCEPT those where Python is not installed :)

- name: Get filesystem data
  shell: python /tmp/filesystem.py
  register: get_fs_data
  become: yes
  become_method: sudo
  become_user: root

- set_fact:
    filesystem_data: "{{ get_fs_data.stdout }}"
  when: get_fs_data.rc == 0

So, my question is ...

How can this be achieved without the usage of that Python script?

I basically want to build a similar list like the one above (hostname, filesystem, mountpoint).

I could execute something like:

df --type=xfs -h --output=source,target

But the output has several lines, one for each file system, and I'm not sure how to handle it on Ansible directly.

U880D
  • 8,601
  • 6
  • 24
  • 40
  • 3
    Python is a basic requirement for running Ansible. The only thing you can do for a server that doesn't have Python is use the `raw` module to run commands on the remote system and collect the output. – larsks Dec 20 '22 at 16:35
  • OK, but how can I handle the output of the df command since it always returns several lines? – Antonio Barrote Dec 20 '22 at 16:45
  • 2
    That seems like a different question. If your `filesystem.py` script already parses the output of df, you could rewrite so that you can collect the output using the `raw` module and then feed it to your script locally for parsing. – larsks Dec 20 '22 at 16:52
  • 2
    If you don't intend to install python on the target, the only usable ansible modules will be `raw` and `script`. Those modules should only be used in theory to install needed deps on the node (i.e. python) to then run ansible "normally". Once python is installed, you are able to gather facts on the node and the information your are looking for is basically available in the `ansible_mounts` variable. Ansible is not the best tool to use if you don't intend to have python at all. – Zeitounator Dec 20 '22 at 16:56

2 Answers2

2

There are more options.

  1. See Ansible collect df output and convert to dictionary from set_fact how to parse the output of df

  2. The simplest option is to use the setup module parameter gather_subset: mounts. Unfortunately, this doesn't work properly for all OS. There is no problem with Linux. The playbook

- hosts: localhost
  gather_facts: false
  tasks:
    - setup:
        gather_subset:
          - distribution
          - mounts
    - debug:
        var: ansible_distribution
    - debug:
        var: ansible_mounts

gives (abridged)

TASK [debug] *********************************************************************************
ok: [localhost] => 
  ansible_distribution: Ubuntu

TASK [debug] *********************************************************************************
ok: [localhost] => 
  ansible_mounts:
  - block_available: 2117913
    block_size: 4096
    block_total: 10013510
    block_used: 7895597
    device: /dev/nvme0n1p6
    fstype: ext4
    inode_available: 1750968
    inode_total: 2564096
    inode_used: 813128
    mount: /
    options: rw,relatime,errors=remount-ro
    size_available: 8674971648
    size_total: 41015336960
    uuid: 505b60e7-509f-46e6-b833-f388df6bb9f0
...

But the same playbook on FreeBSD shows nothing

TASK [debug] *********************************************************************************
ok: [test_11] => 
  ansible_distribution: FreeBSD

TASK [debug] *********************************************************************************
ok: [test_11] => 
  ansible_mounts: []

If gather_subset: mounts works on your systems the report is simple. For example, the task below

    - copy:
        dest: /tmp/ansible_df_all.csv
        content: |
          hostname,filesystem,mountpoint
          {% for hostname in ansible_play_hosts %}
          {% for mount in hostvars[hostname]['ansible_mounts'] %}
          {{ hostname }},{{ mount.device }},{{ mount.mount}}
          {% endfor %}
          {% endfor %}
      run_once: true
      delegate_to: localhost

will create the file at the controller

shell> cat /tmp/ansible_df_all.csv 
hostname,filesystem,mountpoint
localhost,/dev/nvme0n1p6,/
localhost,/dev/nvme0n1p7,/export
localhost,/dev/nvme0n1p2,/boot/efi
Vladimir Botka
  • 58,131
  • 4
  • 32
  • 63
1

Q: Get list of filesystems and mountpoints using Ansible and df without Python installed

As already mentioned within the other answer, there are many options possible.

the output has several lines, one for each file system, and I'm not sure how to handle it on Ansible directly

The data processing can be done on Remote Nodes (data pre-processing, data cleansing), Control Node (data post-processing) or partially on each of them.

Whereby I prefer the answer of @Vladimir Botka and recommend to use it since the data processing is done on the Control Node and in Python

How can this be achieved without the usage of that Python script? (annot.: or any Python installed on the Remote Node)

a lazy approach could be

---
- hosts: test
  become: false
  gather_facts: false # is necessary because setup.py depends on Python too

  tasks:

  - name: Gather raw 'df' output with pre-processing
    raw: "df --type=ext4 -h --output=source,target | tail -n +2 | sed 's/  */,/g'"
    register: result

  - name: Show result as CSV
    debug:
      msg: "{{ inventory_hostname }},{{ item }}"
    loop_control:
      extended: true
      label: "{{ ansible_loop.index0 }}"
    loop: "{{ result.stdout_lines }}"

resulting into an output of

TASK [Show result as CSV] *************
ok: [test.example.com] => (item=0) =>
  msg: test.example.com,/dev/sda3,/
ok: [test.example.com] => (item=1) =>
  msg: test.example.com,/dev/sda4,/tmp
ok: [test.example.com] => (item=2) =>
  msg: test.example.com,/dev/sdc1,/var
ok: [test.example.com] => (item=3) =>
  msg: test.example.com,/dev/sda2,/boot

As noted before, the data pre-processing parts

  • Remove first line from output via | tail -n +2
  • Remove multiple whitepspaces and replace via | sed 's/ */,/g'

could be processed in Ansible and Python on the Control Node, in example

  • Remove first line in stdout_lines (annot.: and as a general approach for --no-headers even for commands which do not provide such)
  • Simply split item on space and re-join all elements of the list with comma (,) via {{ item | split | join(',') }}
  - name: Gather raw 'df' output without pre-processing
    raw: "df --type=ext4 -h --output=source,target"
    register: result

  - name: Show result as CSV
    debug:
      msg: "{{ inventory_hostname }},{{ item | split | join(',')}}"
    loop_control:
      extended: true
      label: "{{ ansible_loop.index0 }}"
    loop: "{{ result.stdout_lines[1:] }}" # with --no-headers

resulting into the same output as before.

Documentation Links

for Ansible

and Linux commands

Further Reading

U880D
  • 8,601
  • 6
  • 24
  • 40