Linux – Set mount option for given mount point with ansible

ansiblelinuxmount

Is there an elegant way to set the mount options for a given mount point by UUID with ansible?

What I want to accomplish is an fstab entry like this:

UUID=d5e3a2e2-a113-4a27-b8d7-801dbf4c6134 / ext4 errors=remount-ro,noatime,user_xattr,acl 0 1

So basically I want to tell ansible: "Set these options for the device on /." but the src parameter of the mount module is required for the state present and I want to use the UUID as the source.

My current solution is posted as an answer below but it will loop over all existing mounts and check whether the path is / or not and therefore produces a lot of skipping outputs.


What I currently do is:

- name: "mount options for /"
  mount:
    path: "/"
    src: "UUID={{ item.uuid }}"
    fstype: "ext4"
    opts: "errors=remount-ro,noatime,user_xattr,acl"
    state: "present"
  with_items:
    - "{{ ansible_mounts }}"
  when: "item.mount == '/'"

This does what I intend to do but it checks all mount points of the host and therefore produces a lot of skipping outputs:

TASK [node : mount options for /] ************************************************************************************************************************
ok: [node001] => (item={u'uuid': u'N/A', u'size_total': 291684016128, u'mount': u'/', u'size_available': 215996313600, u'fstype': u'ext4', u'device': u'/dev/sda2', u'options': u'rw,noatime,nodiratime,errors=remount-ro,user_xattr,acl'})
skipping: [node001] => (item={u'uuid': u'N/A', u'size_total': 527433728, u'mount': u'/boot/efi', u'size_available': 523894784, u'fstype': u'vfat', u'device': u'/dev/sda1', u'options': u'rw'}) 
skipping: [node001] => (item={u'uuid': u'N/A', u'size_total': 10908480831488, u'mount': u'/scr', u'size_available': 2431928762368, u'fstype': u'nfs4', u'device': u'192.168.31.3:/scratch', u'options': u'rw,vers=4,sec=sys,addr=192.168.31.3,clientaddr=192.168.31.41,_netdev'}) 
skipping: [node001] => (item={u'uuid': u'N/A', u'size_total': 10908480831488, u'mount': u'/homes', u'size_available': 2431928762368, u'fstype': u'nfs4', u'device': u'192.168.31.3:/u', u'options': u'rw,vers=4,sec=sys,addr=192.168.31.3,clientaddr=192.168.31.41,_netdev'}) 
skipping: [node001] => (item={u'uuid': u'N/A', u'size_total': 48378511622144, u'mount': u'/mnt/bigdata3', u'size_available': 5786707165184, u'fstype': u'nfs', u'device': u'192.168.31.1:/DATA3/bigdata3', u'options': u'ro,noatime,vers=3,addr=192.168.31.1,_netdev'}) 
skipping: [node001] => (item={u'uuid': u'N/A', u'size_total': 48378511622144, u'mount': u'/mnt/bigdata4', u'size_available': 18173101342720, u'fstype': u'nfs', u'device': u'192.168.31.1:/DATA4/bigdata4', u'options': u'ro,noatime,vers=3,addr=192.168.31.1,_netdev'}) 
skipping: [node001] => (item={u'uuid': u'N/A', u'size_total': 38764865912832, u'mount': u'/mnt/groupdata2', u'size_available': 7847016398848, u'fstype': u'nfs', u'device': u'192.168.31.1:/DATA2/groupdata2', u'options': u'rw,noatime,vers=3,addr=192.168.31.1,_netdev'}) 
skipping: [node001] => (item={u'uuid': u'N/A', u'size_total': 14776486854656, u'mount': u'/mnt/bigdata1', u'size_available': 3801819906048, u'fstype': u'nfs', u'device': u'192.168.31.1:/DATA1/bigdata1', u'options': u'ro,noatime,vers=3,addr=192.168.31.1,_netdev'}) 
skipping: [node001] => (item={u'uuid': u'N/A', u'size_total': 23931377418240, u'mount': u'/mnt/groupdata', u'size_available': 3801819906048, u'fstype': u'nfs', u'device': u'192.168.31.1:/DATA1/groupdata', u'options': u'rw,noatime,vers=3,addr=192.168.31.1,_netdev'}) 
skipping: [node001] => (item={u'uuid': u'N/A', u'size_total': 12593077944320, u'mount': u'/mnt/transfer', u'size_available': 8260916609024, u'fstype': u'nfs', u'device': u'nas:/DATA5/transfer', u'options': u'ro,noatime,vers=4.1,acl,addr=192.168.31.2,clientaddr=192.168.31.41,_netdev'}) 
skipping: [node001] => (item={u'uuid': u'N/A', u'size_total': 40888277401600, u'mount': u'/mnt/transfer2', u'size_available': 25068068405248, u'fstype': u'nfs', u'device': u'nas:/DATA5/transfer2', u'options': u'ro,noatime,vers=4.1,acl,addr=192.168.31.2,clientaddr=192.168.31.41,_netdev'})

This is ugly in my opinion and I wonder if there is a better way to do this.


EDIT

I found the filter json_query which should be able to do what I want. However I couldn't get it to work.

On thier example website I tried this data structure (copied from ansible_facts):

{
    "ansible_mounts": [
        {
            "device": "/dev/sda2", 
            "fstype": "ext4", 
            "mount": "/", 
            "options": "rw,noatime,nodiratime,errors=remount-ro,user_xattr,acl", 
            "size_available": 215995777024, 
            "size_total": 291684016128, 
            "uuid": "N/A"
        }, 
        {
            "device": "/dev/sda1", 
            "fstype": "vfat", 
            "mount": "/boot/efi", 
            "options": "rw", 
            "size_available": 523894784, 
            "size_total": 527433728, 
            "uuid": "N/A"
        }
    ]
}

with this query:

ansible_mounts[?mount == `/`]

and got the desired output:

[
  {
    "device": "/dev/sda2",
    "fstype": "ext4",
    "mount": "/",
    "options": "rw,noatime,nodiratime,errors=remount-ro,user_xattr,acl",
    "size_available": 215995777024,
    "size_total": 291684016128,
    "uuid": "N/A"
  }
]

So I tried this in the playbook:

    src: "UUID={{ item.uuid }}"
  with_items:
    - "{{ ansible_mounts|json_query('ansible_mounts[?mount == `/`]') }}"

but it fails with this message:

The error was: 'ansible.vars.unsafe_proxy.AnsibleUnsafeText object' has no attribute 'uuid'

Best Answer

I managed to get what I want with the json_query filter:

- name: "mount options for /"
  mount:
    path: "/"
    src: "UUID={{ ansible_mounts | json_query('[?mount == `/`] | [0].uuid') }}"
    fstype: "ext4"
    opts: "errors=remount-ro,noatime,user_xattr,acl"
    state: "present"

You will need to install the package python-jmespath.