Repository: ansible/ansible

Issue:
mount module inserts duplicate filesystem into /etc/fstab
##### SUMMARY
I have a filesystem listed in /etc/fstab. When I create a `mount` play to change the parameters for that filesystem and run it, the play adds a new line to fstab that conflicts with the old line there, rather than replacing the old line.

##### ISSUE TYPE
 - Bug Report

##### COMPONENT NAME
`mount`

##### ANSIBLE VERSION
```
ansible 2.6.2
  config file = /home/jik/src/family-orchestration/ansible.cfg
  configured module search path = [u'/home/jik/.ansible/plugins/modules', u'/usr/share/ansible/plugins/modules']
  ansible python module location = /usr/lib/python2.7/dist-packages/ansible
  executable location = /usr/bin/ansible
  python version = 2.7.15rc1 (default, Apr 15 2018, 21:51:34) [GCC 7.3.0]
```

##### CONFIGURATION
```
ANSIBLE_PIPELINING(/home/jik/src/family-orchestration/ansible.cfg) = True
DEFAULT_HOST_LIST(/home/jik/src/family-orchestration/ansible.cfg) = [u'/home/jik/src/family-orchestration/inventory']
DEFAULT_REMOTE_USER(/home/jik/src/family-orchestration/ansible.cfg) = root
```

##### OS / ENVIRONMENT
Both source and target system are Ubuntu 18.04 64-bit.

##### STEPS TO REPRODUCE
I had this line in /etc/fstab, which was previously put there by hand.
```
//nas/photo /home/jik/pictures cifs defaults,credentials=/etc/security/nas.creds,uid=jik,gid=users,forceuid,forcegid,noserverino,_netdev
```
I ran the play shown below. After which, the line shown above was _still_ in /etc/fstab, but this new line was added to the bottom.
```
//nas/photo /home/jik/pictures cifs defaults,credentials=/etc/security/nas.creds,uid=jik,gid=users,forceuid,forcegid,noserverino,_netdev,x-systemd.mount-timeout=0 0 0
```
So now there were two, conflicting lines for the same filesystem in /etc/fstab. That's no good!

```yaml
  - name: mount /home/jik/pictures
    mount:
      src: '//nas/photo'
      path: '/home/jik/pictures'
      fstype: cifs
      opts: 'defaults,credentials=/etc/security/nas.creds,uid=jik,gid=users,forceuid,forcegid,noserverino,_netdev,x-systemd.mount-timeout=0'
      state: mounted
```

##### EXPECTED RESULTS
I expect the `mount` module to notice that the fstab entry it is trying to create has src and path parameters matching an existing filesystem and replace the old entry in fstab with the new one rather than adding a new conflicting entry.

##### ACTUAL RESULTS
The `mount` ansible module corrupted my fstab by adding a conflicting entry to the file.



Discussion:
Files identified in the description:
* [lib/ansible/modules/system/mount.py](https://github.com/ansible/ansible/blob/devel/lib/ansible/modules/system/mount.py)

If these files are inaccurate, please update the `component name` section of the description or use the `!component` bot command.

[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: components_banner --->
cc @jtyr
[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: notify --->
Thanks for opening this issue, @jikquantopian. The problem here is that your original fstab line is missing the 5th and the 6th field. This is why the mount module just [keeps](https://github.com/ansible/ansible/blob/devel/lib/ansible/modules/system/mount.py#L193) the line as it is and add the new one.

The Linux `fstab` documentation is mentioning that the last two fields are optional (`Defaults to zero (don't dump/fsck) if not present.`). SunOS is not mentioning that these fields are optional so I assume that they are obligatory on that platform. I will create a PR to fix this behaviour.

Resolving patch:
diff --git a/lib/ansible/modules/system/mount.py b/lib/ansible/modules/system/mount.py
--- a/lib/ansible/modules/system/mount.py
+++ b/lib/ansible/modules/system/mount.py
@@ -187,10 +187,14 @@ def set_mount(module, args):
 
             continue
 
+        fields = line.split()
+
         # Check if we got a valid line for splitting
+        # (on Linux the 5th and the 6th field is optional)
         if (
-                get_platform() == 'SunOS' and len(line.split()) != 7 or
-                get_platform() != 'SunOS' and len(line.split()) != 6):
+                get_platform() == 'SunOS' and len(fields) != 7 or
+                get_platform() == 'Linux' and len(fields) not in [4, 5, 6] or
+                get_platform() not in ['SunOS', 'Linux'] and len(fields) != 6):
             to_write.append(line)
 
             continue
@@ -206,16 +210,17 @@ def set_mount(module, args):
                 ld['passno'],
                 ld['boot'],
                 ld['opts']
-            ) = line.split()
+            ) = fields
         else:
-            (
-                ld['src'],
-                ld['name'],
-                ld['fstype'],
-                ld['opts'],
-                ld['dump'],
-                ld['passno']
-            ) = line.split()
+            fields_labels = ['src', 'name', 'fstype', 'opts', 'dump', 'passno']
+
+            # The last two fields are optional on Linux so we fill in default values
+            ld['dump'] = 0
+            ld['passno'] = 0
+
+            # Fill in the rest of the available fields
+            for i, field in enumerate(fields):
+                ld[fields_labels[i]] = field
 
         # Check if we found the correct line
         if (


Test patch:
diff --git a/test/integration/targets/mount/tasks/main.yml b/test/integration/targets/mount/tasks/main.yml
--- a/test/integration/targets/mount/tasks/main.yml
+++ b/test/integration/targets/mount/tasks/main.yml
@@ -119,8 +119,6 @@
   shell: mount | grep mount_dest | grep -E -w '(ro|read-only)' | wc -l
   register: remount_options
 
-- debug: var=remount_options
-
 - name: Make sure the filesystem now has the new opts
   assert:
     that:
@@ -248,3 +246,33 @@
       - "swap2_removed['changed']"
       - "not swap2_removed_again['changed']"
   when: ansible_system in ('Linux')
+
+- name: Create fstab record with missing last two fields
+  copy:
+    dest: /etc/fstab
+    content: |
+      //nas/photo /home/jik/pictures cifs defaults,credentials=/etc/security/nas.creds,uid=jik,gid=users,forceuid,forcegid,noserverino,_netdev
+  when: ansible_system in ('Linux')
+
+- name: Try to change the fstab record with the missing last two fields
+  mount:
+    src: //nas/photo
+    path: /home/jik/pictures
+    fstype: cifs
+    opts: defaults,credentials=/etc/security/nas.creds,uid=jik,gid=users,forceuid,forcegid,noserverino,_netdev,x-systemd.mount-timeout=0
+    state: present
+  register: optional_fields_update
+  when: ansible_system in ('Linux')
+
+- name: Get the content of the fstab file
+  shell: cat /etc/fstab
+  register: optional_fields_content
+  when: ansible_system in ('Linux')
+
+- name: Check if the line containing the missing last two fields was changed
+  assert:
+    that:
+      - "optional_fields_update['changed']"
+      - "' 0 0' in optional_fields_content.stdout"
+      - "1 == optional_fields_content.stdout_lines | length"
+  when: ansible_system in ('Linux')