Repository: ansible-collections/community.general

Issue:
Module interfaces_file ignore option line not starting with a space
### Summary

When the  module interfaces_file look for options in the interfaces file, it use the following  regexp (https://github.com/ansible-collections/community.general/blob/main/plugins/modules/interfaces_file.py#L168):
```python
    patt = re.compile(r'^\s+(?P<option>\S+)\s+(?P<value>\S?.*\S)\s*$')
```

It required each option line to start with at least one space character (`^\s+`). The module ignore option lines not starting with  space. Modifying this option results in duplicate lines.

To fix this issue, replace `+` by `*`:
```python
    patt = re.compile(r'^\s*(?P<option>\S+)\s+(?P<value>\S?.*\S)\s*$')
```


### Issue Type

Bug Report

### Component Name

interfaces_file

### Ansible Version

```console (paste below)
ansible [core 2.14.3]
  config file = /home/olivier/xxxxxxx/ansible.cfg
  configured module search path = ['/home/olivier/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
  ansible python module location = /usr/local/lib/python3.11/dist-packages/ansible
  ansible collection location = /home/olivier/.ansible/collections:/usr/share/ansible/collections
  executable location = /usr/local/bin/ansible
  python version = 3.11.1 (main, Dec 31 2022, 10:23:59) [GCC 12.2.0] (/usr/bin/python3)
  jinja version = 3.1.2
  libyaml = True

```


### Community.general Version

```console (paste below)
# /usr/local/lib/python3.11/dist-packages/ansible_collections
Collection        Version
----------------- -------
community.general 6.4.0  
```


### Configuration

_No response_

### OS / Environment

_No response_

### Steps to Reproduce

The task:
```yaml
- name: Set MTU for eth1
  community.general.interfaces_file:
    iface: "eth1"
    option: mtu
    value: "9000"
    state: present
```

My interfaces file before:
```
iface lo inet loopback
auto lo

auto ens192
iface ens192 inet static                                                                                                                                   
address 10.0.0.1
netmask 255.255.255.0
mtu 1500
```


### Expected Results

```console
iface lo inet loopback
auto lo

auto ens192
iface ens192 inet static                                                                                                                                   
address 10.0.0.1
netmask 255.255.255.0
mtu 9000
```

### Actual Results

```console (paste below)
iface lo inet loopback
auto lo

auto ens192
iface ens192 inet static                                                                                                                                   
address 10.0.0.1
netmask 255.255.255.0
mtu 1500
mtu 9000
```


### Code of Conduct

- [X] I agree to follow the Ansible Code of Conduct


Discussion:
Files identified in the description:
* [`plugins/modules/interfaces_file.py`](https://github.com/ansible-collections/community.general/blob/main/plugins/modules/interfaces_file.py)

If these files are incorrect, 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/devel/ISSUE_HELP.md)
<!--- boilerplate: components_banner --->
cc @hryamzik @obourdon
[click here for bot help](https://github.com/ansible/ansibullbot/blob/devel/ISSUE_HELP.md)
<!--- boilerplate: notify --->
Oh my, this is exactly why I implemented initial module rewriting C code to python line by line.

Now I think we should revert changes from @russoz in #3328 as they don't add any value but result in issues like this. That's the third one!

I have actually [raised](https://github.com/ansible-collections/community.general/pull/3328#discussion_r703039738) my concerns in the review.

Resolving patch:
diff --git a/plugins/modules/interfaces_file.py b/plugins/modules/interfaces_file.py
--- a/plugins/modules/interfaces_file.py
+++ b/plugins/modules/interfaces_file.py
@@ -156,20 +156,22 @@
 from ansible.module_utils.common.text.converters import to_bytes
 
 
-def line_dict(line):
+def lineDict(line):
     return {'line': line, 'line_type': 'unknown'}
 
 
-def make_option_dict(line, iface, option, value, address_family):
+def optionDict(line, iface, option, value, address_family):
     return {'line': line, 'iface': iface, 'option': option, 'value': value, 'line_type': 'option', 'address_family': address_family}
 
 
-def get_option_value(line):
-    patt = re.compile(r'^\s+(?P<option>\S+)\s+(?P<value>\S?.*\S)\s*$')
-    match = patt.match(line)
-    if not match:
-        return None, None
-    return match.group("option"), match.group("value")
+def getValueFromLine(s):
+    spaceRe = re.compile(r'\s+')
+    m = list(spaceRe.finditer(s))[-1]
+    valueEnd = m.start()
+    option = s.split()[0]
+    optionStart = s.find(option)
+    optionLen = len(option)
+    return s[optionLen + optionStart:].strip()
 
 
 def read_interfaces_file(module, filename):
@@ -177,27 +179,32 @@ def read_interfaces_file(module, filename):
         return read_interfaces_lines(module, f)
 
 
-def _is_line_processing_none(first_word):
-    return first_word in ("source", "source-dir", "source-directory", "auto", "no-auto-down", "no-scripts") or first_word.startswith("auto-")
-
-
 def read_interfaces_lines(module, line_strings):
     lines = []
     ifaces = {}
-    iface_name = None
-    address_family = None
-    currif = {}
     currently_processing = None
-    for i, line in enumerate(line_strings):
+    i = 0
+    for line in line_strings:
+        i += 1
         words = line.split()
-        if not words or words[0].startswith("#"):
-            lines.append(line_dict(line))
+        if len(words) < 1:
+            lines.append(lineDict(line))
+            continue
+        if words[0][0] == "#":
+            lines.append(lineDict(line))
             continue
         if words[0] == "mapping":
-            lines.append(line_dict(line))
+            # currmap = calloc(1, sizeof *currmap);
+            lines.append(lineDict(line))
             currently_processing = "MAPPING"
-        elif _is_line_processing_none(words[0]):
-            lines.append(line_dict(line))
+        elif words[0] == "source":
+            lines.append(lineDict(line))
+            currently_processing = "NONE"
+        elif words[0] == "source-dir":
+            lines.append(lineDict(line))
+            currently_processing = "NONE"
+        elif words[0] == "source-directory":
+            lines.append(lineDict(line))
             currently_processing = "NONE"
         elif words[0] == "iface":
             currif = {
@@ -220,25 +227,57 @@ def read_interfaces_lines(module, line_strings):
             ifaces[iface_name] = currif
             lines.append({'line': line, 'iface': iface_name, 'line_type': 'iface', 'params': currif, 'address_family': address_family})
             currently_processing = "IFACE"
+        elif words[0] == "auto":
+            lines.append(lineDict(line))
+            currently_processing = "NONE"
+        elif words[0].startswith("allow-"):
+            lines.append(lineDict(line))
+            currently_processing = "NONE"
+        elif words[0] == "no-auto-down":
+            lines.append(lineDict(line))
+            currently_processing = "NONE"
+        elif words[0] == "no-scripts":
+            lines.append(lineDict(line))
+            currently_processing = "NONE"
         else:
             if currently_processing == "IFACE":
-                option_name, value = get_option_value(line)
-                # TODO: if option_name in currif.options
-                lines.append(make_option_dict(line, iface_name, option_name, value, address_family))
+                option_name = words[0]
+                value = getValueFromLine(line)
+                lines.append(optionDict(line, iface_name, option_name, value, address_family))
                 if option_name in ["pre-up", "up", "down", "post-up"]:
                     currif[option_name].append(value)
                 else:
                     currif[option_name] = value
             elif currently_processing == "MAPPING":
-                lines.append(line_dict(line))
+                lines.append(lineDict(line))
             elif currently_processing == "NONE":
-                lines.append(line_dict(line))
+                lines.append(lineDict(line))
             else:
-                module.fail_json(msg="misplaced option %s in line %d" % (line, i + 1))
-
+                module.fail_json(msg="misplaced option %s in line %d" % (line, i))
+                return None, None
     return lines, ifaces
 
 
+def get_interface_options(iface_lines):
+    return [i for i in iface_lines if i['line_type'] == 'option']
+
+
+def get_target_options(iface_options, option):
+    return [i for i in iface_options if i['option'] == option]
+
+
+def update_existing_option_line(target_option, value):
+    old_line = target_option['line']
+    old_value = target_option['value']
+    prefix_start = old_line.find(target_option["option"])
+    optionLen = len(target_option["option"])
+    old_value_position = re.search(r"\s+".join(map(re.escape, old_value.split())), old_line[prefix_start + optionLen:])
+    start = old_value_position.start() + prefix_start + optionLen
+    end = old_value_position.end() + prefix_start + optionLen
+    line = old_line[:start] + value + old_line[end:]
+    return line
+
+
 def set_interface_option(module, lines, iface, option, raw_value, state, address_family=None):
     value = str(raw_value)
     changed = False
@@ -248,41 +287,35 @@ def set_interface_option(module, lines, iface, option, raw_value, state, address
         iface_lines = [item for item in iface_lines
                        if "address_family" in item and item["address_family"] == address_family]
 
-    if not iface_lines:
+    if len(iface_lines) < 1:
         # interface not found
         module.fail_json(msg="Error: interface %s not found" % iface)
+        return changed, None
 
-    iface_options = [il for il in iface_lines if il['line_type'] == 'option']
-    target_options = [io for io in iface_options if io['option'] == option]
+    iface_options = get_interface_options(iface_lines)
+    target_options = get_target_options(iface_options, option)
 
     if state == "present":
-        if not target_options:
+        if len(target_options) < 1:
+            changed = True
             # add new option
             last_line_dict = iface_lines[-1]
-            return add_option_after_line(option, value, iface, lines, last_line_dict, iface_options, address_family)
-
-        if option in ["pre-up", "up", "down", "post-up"] and all(ito['value'] != value for ito in target_options):
-            return add_option_after_line(option, value, iface, lines, target_options[-1], iface_options, address_family)
-
-        # if more than one option found edit the last one
-        if target_options[-1]['value'] != value:
-            changed = True
-            target_option = target_options[-1]
-            old_line = target_option['line']
-            old_value = target_option['value']
-            address_family = target_option['address_family']
-            prefix_start = old_line.find(option)
-            option_len = len(option)
-            old_value_position = re.search(r"\s+".join(map(re.escape, old_value.split())), old_line[prefix_start + option_len:])
-            start = old_value_position.start() + prefix_start + option_len
-            end = old_value_position.end() + prefix_start + option_len
-            line = old_line[:start] + value + old_line[end:]
-            index = len(lines) - lines[::-1].index(target_option) - 1
-            lines[index] = make_option_dict(line, iface, option, value, address_family)
-            return changed, lines
-
-    if state == "absent":
-        if target_options:
+            changed, lines = addOptionAfterLine(option, value, iface, lines, last_line_dict, iface_options, address_family)
+        else:
+            if option in ["pre-up", "up", "down", "post-up"]:
+                if len(list(filter(lambda i: i['value'] == value, target_options))) < 1:
+                    changed, lines = addOptionAfterLine(option, value, iface, lines, target_options[-1], iface_options, address_family)
+            else:
+                # if more than one option found edit the last one
+                if target_options[-1]['value'] != value:
+                    changed = True
+                    target_option = target_options[-1]
+                    line = update_existing_option_line(target_option, value)
+                    address_family = target_option['address_family']
+                    index = len(lines) - lines[::-1].index(target_option) - 1
+                    lines[index] = optionDict(line, iface, option, value, address_family)
+    elif state == "absent":
+        if len(target_options) >= 1:
             if option in ["pre-up", "up", "down", "post-up"] and value is not None and value != "None":
                 for target_option in [ito for ito in target_options if ito['value'] == value]:
                     changed = True
@@ -291,11 +324,13 @@ def set_interface_option(module, lines, iface, option, raw_value, state, address
                 changed = True
                 for target_option in target_options:
                     lines = [ln for ln in lines if ln != target_option]
+    else:
+        module.fail_json(msg="Error: unsupported state %s, has to be either present or absent" % state)
 
     return changed, lines
 
 
-def add_option_after_line(option, value, iface, lines, last_line_dict, iface_options, address_family):
+def addOptionAfterLine(option, value, iface, lines, last_line_dict, iface_options, address_family):
     # Changing method of interface is not an addition
     if option == 'method':
         changed = False
@@ -311,18 +346,19 @@ def add_option_after_line(option, value, iface, lines, last_line_dict, iface_opt
     suffix_start = last_line.rfind(last_line.split()[-1]) + len(last_line.split()[-1])
     prefix = last_line[:prefix_start]
 
-    if not iface_options:
+    if len(iface_options) < 1:
         # interface has no options, ident
         prefix += "    "
 
     line = prefix + "%s %s" % (option, value) + last_line[suffix_start:]
-    option_dict = make_option_dict(line, iface, option, value, address_family)
+    option_dict = optionDict(line, iface, option, value, address_family)
     index = len(lines) - lines[::-1].index(last_line_dict)
     lines.insert(index, option_dict)
     return True, lines
 
 
 def write_changes(module, lines, dest):
+
     tmpfd, tmpfile = tempfile.mkstemp()
     with os.fdopen(tmpfd, 'wb') as f:
         f.write(to_bytes(''.join(lines), errors='surrogate_or_strict'))


Test patch:
diff --git a/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces.test_no_changes b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces.test_no_changes
new file mode 100644
--- /dev/null
+++ b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces.test_no_changes
@@ -0,0 +1,8 @@
+iface lo inet loopback
+auto lo
+
+auto eth0
+iface eth0 inet static                                                                                                                                   
+address 10.0.0.1
+netmask 255.255.255.0
+mtu 1500
diff --git a/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces.test_no_changes.json b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces.test_no_changes.json
new file mode 100644
--- /dev/null
+++ b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces.test_no_changes.json
@@ -0,0 +1,21 @@
+{
+    "eth0": {
+        "address": "10.0.0.1",
+        "address_family": "inet",
+        "down": [],
+        "method": "static",
+        "mtu": "1500",
+        "netmask": "255.255.255.0",
+        "post-up": [],
+        "pre-up": [],
+        "up": []
+    },
+    "lo": {
+        "address_family": "inet",
+        "down": [],
+        "method": "loopback",
+        "post-up": [],
+        "pre-up": [],
+        "up": []
+    }
+}
diff --git a/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces.test_no_changes.json.license b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces.test_no_changes.json.license
new file mode 100644
--- /dev/null
+++ b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces.test_no_changes.json.license
@@ -0,0 +1,3 @@
+GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
+SPDX-License-Identifier: GPL-3.0-or-later
+SPDX-FileCopyrightText: Ansible Project
diff --git a/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces.test_no_changes.license b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces.test_no_changes.license
new file mode 100644
--- /dev/null
+++ b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces.test_no_changes.license
@@ -0,0 +1,3 @@
+GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
+SPDX-License-Identifier: GPL-3.0-or-later
+SPDX-FileCopyrightText: Ansible Project
diff --git a/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_add_aggi_up b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_add_aggi_up
new file mode 100644
--- /dev/null
+++ b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_add_aggi_up
@@ -0,0 +1,8 @@
+iface lo inet loopback
+auto lo
+
+auto eth0
+iface eth0 inet static                                                                                                                                   
+address 10.0.0.1
+netmask 255.255.255.0
+mtu 1500
diff --git a/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_add_aggi_up.exceptions.txt b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_add_aggi_up.exceptions.txt
new file mode 100644
--- /dev/null
+++ b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_add_aggi_up.exceptions.txt
@@ -0,0 +1,8 @@
+[0] fail_json message: Error: interface aggi not found
+options:
+{
+    "iface": "aggi",
+    "option": "up",
+    "state": "present",
+    "value": "route add -net 224.0.0.0 netmask 240.0.0.0 dev aggi"
+}
diff --git a/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_add_aggi_up.exceptions.txt.license b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_add_aggi_up.exceptions.txt.license
new file mode 100644
--- /dev/null
+++ b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_add_aggi_up.exceptions.txt.license
@@ -0,0 +1,3 @@
+GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
+SPDX-License-Identifier: GPL-3.0-or-later
+SPDX-FileCopyrightText: Ansible Project
diff --git a/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_add_aggi_up.json b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_add_aggi_up.json
new file mode 100644
--- /dev/null
+++ b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_add_aggi_up.json
@@ -0,0 +1,21 @@
+{
+    "eth0": {
+        "address": "10.0.0.1",
+        "address_family": "inet",
+        "down": [],
+        "method": "static",
+        "mtu": "1500",
+        "netmask": "255.255.255.0",
+        "post-up": [],
+        "pre-up": [],
+        "up": []
+    },
+    "lo": {
+        "address_family": "inet",
+        "down": [],
+        "method": "loopback",
+        "post-up": [],
+        "pre-up": [],
+        "up": []
+    }
+}
diff --git a/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_add_aggi_up.json.license b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_add_aggi_up.json.license
new file mode 100644
--- /dev/null
+++ b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_add_aggi_up.json.license
@@ -0,0 +1,3 @@
+GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
+SPDX-License-Identifier: GPL-3.0-or-later
+SPDX-FileCopyrightText: Ansible Project
diff --git a/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_add_aggi_up.license b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_add_aggi_up.license
new file mode 100644
--- /dev/null
+++ b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_add_aggi_up.license
@@ -0,0 +1,3 @@
+GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
+SPDX-License-Identifier: GPL-3.0-or-later
+SPDX-FileCopyrightText: Ansible Project
diff --git a/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_add_aggi_up_twice b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_add_aggi_up_twice
new file mode 100644
--- /dev/null
+++ b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_add_aggi_up_twice
@@ -0,0 +1,8 @@
+iface lo inet loopback
+auto lo
+
+auto eth0
+iface eth0 inet static                                                                                                                                   
+address 10.0.0.1
+netmask 255.255.255.0
+mtu 1500
diff --git a/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_add_aggi_up_twice.exceptions.txt b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_add_aggi_up_twice.exceptions.txt
new file mode 100644
--- /dev/null
+++ b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_add_aggi_up_twice.exceptions.txt
@@ -0,0 +1,17 @@
+[0] fail_json message: Error: interface aggi not found
+options:
+{
+    "iface": "aggi",
+    "option": "up",
+    "state": "present",
+    "value": "route add -net 224.0.0.0 netmask 240.0.0.0 dev aggi"
+}
+=====
+[1] fail_json message: Error: interface aggi not found
+options:
+{
+    "iface": "aggi",
+    "option": "up",
+    "state": "present",
+    "value": "route add -net 224.0.0.0 netmask 240.0.0.0 dev aggi"
+}
diff --git a/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_add_aggi_up_twice.exceptions.txt.license b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_add_aggi_up_twice.exceptions.txt.license
new file mode 100644
--- /dev/null
+++ b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_add_aggi_up_twice.exceptions.txt.license
@@ -0,0 +1,3 @@
+GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
+SPDX-License-Identifier: GPL-3.0-or-later
+SPDX-FileCopyrightText: Ansible Project
diff --git a/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_add_aggi_up_twice.json b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_add_aggi_up_twice.json
new file mode 100644
--- /dev/null
+++ b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_add_aggi_up_twice.json
@@ -0,0 +1,21 @@
+{
+    "eth0": {
+        "address": "10.0.0.1",
+        "address_family": "inet",
+        "down": [],
+        "method": "static",
+        "mtu": "1500",
+        "netmask": "255.255.255.0",
+        "post-up": [],
+        "pre-up": [],
+        "up": []
+    },
+    "lo": {
+        "address_family": "inet",
+        "down": [],
+        "method": "loopback",
+        "post-up": [],
+        "pre-up": [],
+        "up": []
+    }
+}
diff --git a/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_add_aggi_up_twice.json.license b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_add_aggi_up_twice.json.license
new file mode 100644
--- /dev/null
+++ b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_add_aggi_up_twice.json.license
@@ -0,0 +1,3 @@
+GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
+SPDX-License-Identifier: GPL-3.0-or-later
+SPDX-FileCopyrightText: Ansible Project
diff --git a/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_add_aggi_up_twice.license b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_add_aggi_up_twice.license
new file mode 100644
--- /dev/null
+++ b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_add_aggi_up_twice.license
@@ -0,0 +1,3 @@
+GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
+SPDX-License-Identifier: GPL-3.0-or-later
+SPDX-FileCopyrightText: Ansible Project
diff --git a/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_add_and_delete_aggi_up b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_add_and_delete_aggi_up
new file mode 100644
--- /dev/null
+++ b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_add_and_delete_aggi_up
@@ -0,0 +1,8 @@
+iface lo inet loopback
+auto lo
+
+auto eth0
+iface eth0 inet static                                                                                                                                   
+address 10.0.0.1
+netmask 255.255.255.0
+mtu 1500
diff --git a/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_add_and_delete_aggi_up.exceptions.txt b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_add_and_delete_aggi_up.exceptions.txt
new file mode 100644
--- /dev/null
+++ b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_add_and_delete_aggi_up.exceptions.txt
@@ -0,0 +1,17 @@
+[0] fail_json message: Error: interface aggi not found
+options:
+{
+    "iface": "aggi",
+    "option": "up",
+    "state": "present",
+    "value": "route add -net 224.0.0.0 netmask 240.0.0.0 dev aggi"
+}
+=====
+[1] fail_json message: Error: interface aggi not found
+options:
+{
+    "iface": "aggi",
+    "option": "up",
+    "state": "absent",
+    "value": null
+}
diff --git a/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_add_and_delete_aggi_up.exceptions.txt.license b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_add_and_delete_aggi_up.exceptions.txt.license
new file mode 100644
--- /dev/null
+++ b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_add_and_delete_aggi_up.exceptions.txt.license
@@ -0,0 +1,3 @@
+GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
+SPDX-License-Identifier: GPL-3.0-or-later
+SPDX-FileCopyrightText: Ansible Project
diff --git a/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_add_and_delete_aggi_up.json b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_add_and_delete_aggi_up.json
new file mode 100644
--- /dev/null
+++ b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_add_and_delete_aggi_up.json
@@ -0,0 +1,21 @@
+{
+    "eth0": {
+        "address": "10.0.0.1",
+        "address_family": "inet",
+        "down": [],
+        "method": "static",
+        "mtu": "1500",
+        "netmask": "255.255.255.0",
+        "post-up": [],
+        "pre-up": [],
+        "up": []
+    },
+    "lo": {
+        "address_family": "inet",
+        "down": [],
+        "method": "loopback",
+        "post-up": [],
+        "pre-up": [],
+        "up": []
+    }
+}
diff --git a/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_add_and_delete_aggi_up.json.license b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_add_and_delete_aggi_up.json.license
new file mode 100644
--- /dev/null
+++ b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_add_and_delete_aggi_up.json.license
@@ -0,0 +1,3 @@
+GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
+SPDX-License-Identifier: GPL-3.0-or-later
+SPDX-FileCopyrightText: Ansible Project
diff --git a/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_add_and_delete_aggi_up.license b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_add_and_delete_aggi_up.license
new file mode 100644
--- /dev/null
+++ b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_add_and_delete_aggi_up.license
@@ -0,0 +1,3 @@
+GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
+SPDX-License-Identifier: GPL-3.0-or-later
+SPDX-FileCopyrightText: Ansible Project
diff --git a/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_aggi_remove_dup b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_aggi_remove_dup
new file mode 100644
--- /dev/null
+++ b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_aggi_remove_dup
@@ -0,0 +1,8 @@
+iface lo inet loopback
+auto lo
+
+auto eth0
+iface eth0 inet static                                                                                                                                   
+address 10.0.0.1
+netmask 255.255.255.0
+mtu 1500
diff --git a/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_aggi_remove_dup.exceptions.txt b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_aggi_remove_dup.exceptions.txt
new file mode 100644
--- /dev/null
+++ b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_aggi_remove_dup.exceptions.txt
@@ -0,0 +1,17 @@
+[0] fail_json message: Error: interface aggi not found
+options:
+{
+    "iface": "aggi",
+    "option": "up",
+    "state": "absent",
+    "value": null
+}
+=====
+[1] fail_json message: Error: interface aggi not found
+options:
+{
+    "iface": "aggi",
+    "option": "up",
+    "state": "present",
+    "value": "route add -net 224.0.0.0 netmask 240.0.0.0 dev aggi"
+}
diff --git a/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_aggi_remove_dup.exceptions.txt.license b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_aggi_remove_dup.exceptions.txt.license
new file mode 100644
--- /dev/null
+++ b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_aggi_remove_dup.exceptions.txt.license
@@ -0,0 +1,3 @@
+GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
+SPDX-License-Identifier: GPL-3.0-or-later
+SPDX-FileCopyrightText: Ansible Project
diff --git a/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_aggi_remove_dup.json b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_aggi_remove_dup.json
new file mode 100644
--- /dev/null
+++ b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_aggi_remove_dup.json
@@ -0,0 +1,21 @@
+{
+    "eth0": {
+        "address": "10.0.0.1",
+        "address_family": "inet",
+        "down": [],
+        "method": "static",
+        "mtu": "1500",
+        "netmask": "255.255.255.0",
+        "post-up": [],
+        "pre-up": [],
+        "up": []
+    },
+    "lo": {
+        "address_family": "inet",
+        "down": [],
+        "method": "loopback",
+        "post-up": [],
+        "pre-up": [],
+        "up": []
+    }
+}
diff --git a/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_aggi_remove_dup.json.license b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_aggi_remove_dup.json.license
new file mode 100644
--- /dev/null
+++ b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_aggi_remove_dup.json.license
@@ -0,0 +1,3 @@
+GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
+SPDX-License-Identifier: GPL-3.0-or-later
+SPDX-FileCopyrightText: Ansible Project
diff --git a/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_aggi_remove_dup.license b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_aggi_remove_dup.license
new file mode 100644
--- /dev/null
+++ b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_aggi_remove_dup.license
@@ -0,0 +1,3 @@
+GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
+SPDX-License-Identifier: GPL-3.0-or-later
+SPDX-FileCopyrightText: Ansible Project
diff --git a/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_change_ipv4 b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_change_ipv4
new file mode 100644
--- /dev/null
+++ b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_change_ipv4
@@ -0,0 +1,8 @@
+iface lo inet loopback
+auto lo
+
+auto eth0
+iface eth0 inet static                                                                                                                                   
+address 192.168.0.42
+netmask 255.255.255.0
+mtu 1500
diff --git a/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_change_ipv4.exceptions.txt b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_change_ipv4.exceptions.txt
new file mode 100644
diff --git a/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_change_ipv4.exceptions.txt.license b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_change_ipv4.exceptions.txt.license
new file mode 100644
--- /dev/null
+++ b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_change_ipv4.exceptions.txt.license
@@ -0,0 +1,3 @@
+GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
+SPDX-License-Identifier: GPL-3.0-or-later
+SPDX-FileCopyrightText: Ansible Project
diff --git a/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_change_ipv4.json b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_change_ipv4.json
new file mode 100644
--- /dev/null
+++ b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_change_ipv4.json
@@ -0,0 +1,21 @@
+{
+    "eth0": {
+        "address": "10.0.0.1",
+        "address_family": "inet",
+        "down": [],
+        "method": "static",
+        "mtu": "1500",
+        "netmask": "255.255.255.0",
+        "post-up": [],
+        "pre-up": [],
+        "up": []
+    },
+    "lo": {
+        "address_family": "inet",
+        "down": [],
+        "method": "loopback",
+        "post-up": [],
+        "pre-up": [],
+        "up": []
+    }
+}
diff --git a/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_change_ipv4.json.license b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_change_ipv4.json.license
new file mode 100644
--- /dev/null
+++ b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_change_ipv4.json.license
@@ -0,0 +1,3 @@
+GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
+SPDX-License-Identifier: GPL-3.0-or-later
+SPDX-FileCopyrightText: Ansible Project
diff --git a/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_change_ipv4.license b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_change_ipv4.license
new file mode 100644
--- /dev/null
+++ b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_change_ipv4.license
@@ -0,0 +1,3 @@
+GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
+SPDX-License-Identifier: GPL-3.0-or-later
+SPDX-FileCopyrightText: Ansible Project
diff --git a/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_change_ipv4_post_up b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_change_ipv4_post_up
new file mode 100644
--- /dev/null
+++ b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_change_ipv4_post_up
@@ -0,0 +1,9 @@
+iface lo inet loopback
+auto lo
+
+auto eth0
+iface eth0 inet static                                                                                                                                   
+address 10.0.0.1
+netmask 255.255.255.0
+mtu 1500
+post-up XXXX_ipv4
diff --git a/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_change_ipv4_post_up.exceptions.txt b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_change_ipv4_post_up.exceptions.txt
new file mode 100644
diff --git a/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_change_ipv4_post_up.exceptions.txt.license b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_change_ipv4_post_up.exceptions.txt.license
new file mode 100644
--- /dev/null
+++ b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_change_ipv4_post_up.exceptions.txt.license
@@ -0,0 +1,3 @@
+GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
+SPDX-License-Identifier: GPL-3.0-or-later
+SPDX-FileCopyrightText: Ansible Project
diff --git a/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_change_ipv4_post_up.json b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_change_ipv4_post_up.json
new file mode 100644
--- /dev/null
+++ b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_change_ipv4_post_up.json
@@ -0,0 +1,21 @@
+{
+    "eth0": {
+        "address": "10.0.0.1",
+        "address_family": "inet",
+        "down": [],
+        "method": "static",
+        "mtu": "1500",
+        "netmask": "255.255.255.0",
+        "post-up": [],
+        "pre-up": [],
+        "up": []
+    },
+    "lo": {
+        "address_family": "inet",
+        "down": [],
+        "method": "loopback",
+        "post-up": [],
+        "pre-up": [],
+        "up": []
+    }
+}
diff --git a/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_change_ipv4_post_up.json.license b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_change_ipv4_post_up.json.license
new file mode 100644
--- /dev/null
+++ b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_change_ipv4_post_up.json.license
@@ -0,0 +1,3 @@
+GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
+SPDX-License-Identifier: GPL-3.0-or-later
+SPDX-FileCopyrightText: Ansible Project
diff --git a/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_change_ipv4_post_up.license b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_change_ipv4_post_up.license
new file mode 100644
--- /dev/null
+++ b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_change_ipv4_post_up.license
@@ -0,0 +1,3 @@
+GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
+SPDX-License-Identifier: GPL-3.0-or-later
+SPDX-FileCopyrightText: Ansible Project
diff --git a/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_change_ipv4_pre_up b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_change_ipv4_pre_up
new file mode 100644
--- /dev/null
+++ b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_change_ipv4_pre_up
@@ -0,0 +1,9 @@
+iface lo inet loopback
+auto lo
+
+auto eth0
+iface eth0 inet static                                                                                                                                   
+address 10.0.0.1
+netmask 255.255.255.0
+mtu 1500
+pre-up XXXX_ipv4
diff --git a/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_change_ipv4_pre_up.exceptions.txt b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_change_ipv4_pre_up.exceptions.txt
new file mode 100644
diff --git a/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_change_ipv4_pre_up.exceptions.txt.license b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_change_ipv4_pre_up.exceptions.txt.license
new file mode 100644
--- /dev/null
+++ b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_change_ipv4_pre_up.exceptions.txt.license
@@ -0,0 +1,3 @@
+GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
+SPDX-License-Identifier: GPL-3.0-or-later
+SPDX-FileCopyrightText: Ansible Project
diff --git a/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_change_ipv4_pre_up.json b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_change_ipv4_pre_up.json
new file mode 100644
--- /dev/null
+++ b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_change_ipv4_pre_up.json
@@ -0,0 +1,21 @@
+{
+    "eth0": {
+        "address": "10.0.0.1",
+        "address_family": "inet",
+        "down": [],
+        "method": "static",
+        "mtu": "1500",
+        "netmask": "255.255.255.0",
+        "post-up": [],
+        "pre-up": [],
+        "up": []
+    },
+    "lo": {
+        "address_family": "inet",
+        "down": [],
+        "method": "loopback",
+        "post-up": [],
+        "pre-up": [],
+        "up": []
+    }
+}
diff --git a/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_change_ipv4_pre_up.json.license b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_change_ipv4_pre_up.json.license
new file mode 100644
--- /dev/null
+++ b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_change_ipv4_pre_up.json.license
@@ -0,0 +1,3 @@
+GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
+SPDX-License-Identifier: GPL-3.0-or-later
+SPDX-FileCopyrightText: Ansible Project
diff --git a/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_change_ipv4_pre_up.license b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_change_ipv4_pre_up.license
new file mode 100644
--- /dev/null
+++ b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_change_ipv4_pre_up.license
@@ -0,0 +1,3 @@
+GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
+SPDX-License-Identifier: GPL-3.0-or-later
+SPDX-FileCopyrightText: Ansible Project
diff --git a/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_change_ipv6 b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_change_ipv6
new file mode 100644
--- /dev/null
+++ b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_change_ipv6
@@ -0,0 +1,8 @@
+iface lo inet loopback
+auto lo
+
+auto eth0
+iface eth0 inet static                                                                                                                                   
+address 10.0.0.1
+netmask 255.255.255.0
+mtu 1500
diff --git a/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_change_ipv6.exceptions.txt b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_change_ipv6.exceptions.txt
new file mode 100644
--- /dev/null
+++ b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_change_ipv6.exceptions.txt
@@ -0,0 +1,9 @@
+fail_json message: Error: interface eth0 not found
+options:
+{
+    "address_family": "inet6",
+    "iface": "eth0",
+    "option": "address",
+    "state": "present",
+    "value": "fc00::42"
+}
diff --git a/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_change_ipv6.exceptions.txt.license b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_change_ipv6.exceptions.txt.license
new file mode 100644
--- /dev/null
+++ b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_change_ipv6.exceptions.txt.license
@@ -0,0 +1,3 @@
+GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
+SPDX-License-Identifier: GPL-3.0-or-later
+SPDX-FileCopyrightText: Ansible Project
diff --git a/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_change_ipv6.json b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_change_ipv6.json
new file mode 100644
--- /dev/null
+++ b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_change_ipv6.json
@@ -0,0 +1,21 @@
+{
+    "eth0": {
+        "address": "10.0.0.1",
+        "address_family": "inet",
+        "down": [],
+        "method": "static",
+        "mtu": "1500",
+        "netmask": "255.255.255.0",
+        "post-up": [],
+        "pre-up": [],
+        "up": []
+    },
+    "lo": {
+        "address_family": "inet",
+        "down": [],
+        "method": "loopback",
+        "post-up": [],
+        "pre-up": [],
+        "up": []
+    }
+}
diff --git a/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_change_ipv6.json.license b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_change_ipv6.json.license
new file mode 100644
--- /dev/null
+++ b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_change_ipv6.json.license
@@ -0,0 +1,3 @@
+GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
+SPDX-License-Identifier: GPL-3.0-or-later
+SPDX-FileCopyrightText: Ansible Project
diff --git a/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_change_ipv6.license b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_change_ipv6.license
new file mode 100644
--- /dev/null
+++ b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_change_ipv6.license
@@ -0,0 +1,3 @@
+GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
+SPDX-License-Identifier: GPL-3.0-or-later
+SPDX-FileCopyrightText: Ansible Project
diff --git a/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_change_ipv6_post_up b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_change_ipv6_post_up
new file mode 100644
--- /dev/null
+++ b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_change_ipv6_post_up
@@ -0,0 +1,8 @@
+iface lo inet loopback
+auto lo
+
+auto eth0
+iface eth0 inet static                                                                                                                                   
+address 10.0.0.1
+netmask 255.255.255.0
+mtu 1500
diff --git a/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_change_ipv6_post_up.exceptions.txt b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_change_ipv6_post_up.exceptions.txt
new file mode 100644
--- /dev/null
+++ b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_change_ipv6_post_up.exceptions.txt
@@ -0,0 +1,9 @@
+fail_json message: Error: interface eth0 not found
+options:
+{
+    "address_family": "inet6",
+    "iface": "eth0",
+    "option": "post-up",
+    "state": "present",
+    "value": "XXXX_ipv6"
+}
diff --git a/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_change_ipv6_post_up.exceptions.txt.license b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_change_ipv6_post_up.exceptions.txt.license
new file mode 100644
--- /dev/null
+++ b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_change_ipv6_post_up.exceptions.txt.license
@@ -0,0 +1,3 @@
+GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
+SPDX-License-Identifier: GPL-3.0-or-later
+SPDX-FileCopyrightText: Ansible Project
diff --git a/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_change_ipv6_post_up.json b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_change_ipv6_post_up.json
new file mode 100644
--- /dev/null
+++ b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_change_ipv6_post_up.json
@@ -0,0 +1,21 @@
+{
+    "eth0": {
+        "address": "10.0.0.1",
+        "address_family": "inet",
+        "down": [],
+        "method": "static",
+        "mtu": "1500",
+        "netmask": "255.255.255.0",
+        "post-up": [],
+        "pre-up": [],
+        "up": []
+    },
+    "lo": {
+        "address_family": "inet",
+        "down": [],
+        "method": "loopback",
+        "post-up": [],
+        "pre-up": [],
+        "up": []
+    }
+}
diff --git a/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_change_ipv6_post_up.json.license b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_change_ipv6_post_up.json.license
new file mode 100644
--- /dev/null
+++ b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_change_ipv6_post_up.json.license
@@ -0,0 +1,3 @@
+GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
+SPDX-License-Identifier: GPL-3.0-or-later
+SPDX-FileCopyrightText: Ansible Project
diff --git a/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_change_ipv6_post_up.license b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_change_ipv6_post_up.license
new file mode 100644
--- /dev/null
+++ b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_change_ipv6_post_up.license
@@ -0,0 +1,3 @@
+GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
+SPDX-License-Identifier: GPL-3.0-or-later
+SPDX-FileCopyrightText: Ansible Project
diff --git a/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_change_ipv6_pre_up b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_change_ipv6_pre_up
new file mode 100644
--- /dev/null
+++ b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_change_ipv6_pre_up
@@ -0,0 +1,8 @@
+iface lo inet loopback
+auto lo
+
+auto eth0
+iface eth0 inet static                                                                                                                                   
+address 10.0.0.1
+netmask 255.255.255.0
+mtu 1500
diff --git a/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_change_ipv6_pre_up.exceptions.txt b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_change_ipv6_pre_up.exceptions.txt
new file mode 100644
--- /dev/null
+++ b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_change_ipv6_pre_up.exceptions.txt
@@ -0,0 +1,9 @@
+fail_json message: Error: interface eth0 not found
+options:
+{
+    "address_family": "inet6",
+    "iface": "eth0",
+    "option": "pre-up",
+    "state": "present",
+    "value": "XXXX_ipv6"
+}
diff --git a/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_change_ipv6_pre_up.exceptions.txt.license b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_change_ipv6_pre_up.exceptions.txt.license
new file mode 100644
--- /dev/null
+++ b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_change_ipv6_pre_up.exceptions.txt.license
@@ -0,0 +1,3 @@
+GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
+SPDX-License-Identifier: GPL-3.0-or-later
+SPDX-FileCopyrightText: Ansible Project
diff --git a/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_change_ipv6_pre_up.json b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_change_ipv6_pre_up.json
new file mode 100644
--- /dev/null
+++ b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_change_ipv6_pre_up.json
@@ -0,0 +1,21 @@
+{
+    "eth0": {
+        "address": "10.0.0.1",
+        "address_family": "inet",
+        "down": [],
+        "method": "static",
+        "mtu": "1500",
+        "netmask": "255.255.255.0",
+        "post-up": [],
+        "pre-up": [],
+        "up": []
+    },
+    "lo": {
+        "address_family": "inet",
+        "down": [],
+        "method": "loopback",
+        "post-up": [],
+        "pre-up": [],
+        "up": []
+    }
+}
diff --git a/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_change_ipv6_pre_up.json.license b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_change_ipv6_pre_up.json.license
new file mode 100644
--- /dev/null
+++ b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_change_ipv6_pre_up.json.license
@@ -0,0 +1,3 @@
+GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
+SPDX-License-Identifier: GPL-3.0-or-later
+SPDX-FileCopyrightText: Ansible Project
diff --git a/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_change_ipv6_pre_up.license b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_change_ipv6_pre_up.license
new file mode 100644
--- /dev/null
+++ b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_change_ipv6_pre_up.license
@@ -0,0 +1,3 @@
+GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
+SPDX-License-Identifier: GPL-3.0-or-later
+SPDX-FileCopyrightText: Ansible Project
diff --git a/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_change_method b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_change_method
new file mode 100644
--- /dev/null
+++ b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_change_method
@@ -0,0 +1,8 @@
+iface lo inet loopback
+auto lo
+
+auto eth0
+iface eth0 inet static                                                                                                                                   
+address 10.0.0.1
+netmask 255.255.255.0
+mtu 1500
diff --git a/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_change_method.exceptions.txt b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_change_method.exceptions.txt
new file mode 100644
--- /dev/null
+++ b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_change_method.exceptions.txt
@@ -0,0 +1,8 @@
+fail_json message: Error: interface eth1 not found
+options:
+{
+    "iface": "eth1",
+    "option": "method",
+    "state": "present",
+    "value": "dhcp"
+}
diff --git a/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_change_method.exceptions.txt.license b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_change_method.exceptions.txt.license
new file mode 100644
--- /dev/null
+++ b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_change_method.exceptions.txt.license
@@ -0,0 +1,3 @@
+GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
+SPDX-License-Identifier: GPL-3.0-or-later
+SPDX-FileCopyrightText: Ansible Project
diff --git a/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_change_method.json b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_change_method.json
new file mode 100644
--- /dev/null
+++ b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_change_method.json
@@ -0,0 +1,21 @@
+{
+    "eth0": {
+        "address": "10.0.0.1",
+        "address_family": "inet",
+        "down": [],
+        "method": "static",
+        "mtu": "1500",
+        "netmask": "255.255.255.0",
+        "post-up": [],
+        "pre-up": [],
+        "up": []
+    },
+    "lo": {
+        "address_family": "inet",
+        "down": [],
+        "method": "loopback",
+        "post-up": [],
+        "pre-up": [],
+        "up": []
+    }
+}
diff --git a/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_change_method.json.license b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_change_method.json.license
new file mode 100644
--- /dev/null
+++ b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_change_method.json.license
@@ -0,0 +1,3 @@
+GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
+SPDX-License-Identifier: GPL-3.0-or-later
+SPDX-FileCopyrightText: Ansible Project
diff --git a/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_change_method.license b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_change_method.license
new file mode 100644
--- /dev/null
+++ b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_change_method.license
@@ -0,0 +1,3 @@
+GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
+SPDX-License-Identifier: GPL-3.0-or-later
+SPDX-FileCopyrightText: Ansible Project
diff --git a/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_revert b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_revert
new file mode 100644
--- /dev/null
+++ b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_revert
@@ -0,0 +1,7 @@
+iface lo inet loopback
+auto lo
+
+auto eth0
+iface eth0 inet static                                                                                                                                   
+address 10.0.0.1
+netmask 255.255.255.0
diff --git a/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_revert.exceptions.txt b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_revert.exceptions.txt
new file mode 100644
diff --git a/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_revert.exceptions.txt.license b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_revert.exceptions.txt.license
new file mode 100644
--- /dev/null
+++ b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_revert.exceptions.txt.license
@@ -0,0 +1,3 @@
+GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
+SPDX-License-Identifier: GPL-3.0-or-later
+SPDX-FileCopyrightText: Ansible Project
diff --git a/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_revert.json b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_revert.json
new file mode 100644
--- /dev/null
+++ b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_revert.json
@@ -0,0 +1,21 @@
+{
+    "eth0": {
+        "address": "10.0.0.1",
+        "address_family": "inet",
+        "down": [],
+        "method": "static",
+        "mtu": "1500",
+        "netmask": "255.255.255.0",
+        "post-up": [],
+        "pre-up": [],
+        "up": []
+    },
+    "lo": {
+        "address_family": "inet",
+        "down": [],
+        "method": "loopback",
+        "post-up": [],
+        "pre-up": [],
+        "up": []
+    }
+}
diff --git a/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_revert.json.license b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_revert.json.license
new file mode 100644
--- /dev/null
+++ b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_revert.json.license
@@ -0,0 +1,3 @@
+GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
+SPDX-License-Identifier: GPL-3.0-or-later
+SPDX-FileCopyrightText: Ansible Project
diff --git a/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_revert.license b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_revert.license
new file mode 100644
--- /dev/null
+++ b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_revert.license
@@ -0,0 +1,3 @@
+GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
+SPDX-License-Identifier: GPL-3.0-or-later
+SPDX-FileCopyrightText: Ansible Project
diff --git a/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_set_aggi_and_eth0_mtu b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_set_aggi_and_eth0_mtu
new file mode 100644
--- /dev/null
+++ b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_set_aggi_and_eth0_mtu
@@ -0,0 +1,8 @@
+iface lo inet loopback
+auto lo
+
+auto eth0
+iface eth0 inet static                                                                                                                                   
+address 10.0.0.1
+netmask 255.255.255.0
+mtu 1350
diff --git a/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_set_aggi_and_eth0_mtu.exceptions.txt b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_set_aggi_and_eth0_mtu.exceptions.txt
new file mode 100644
--- /dev/null
+++ b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_set_aggi_and_eth0_mtu.exceptions.txt
@@ -0,0 +1,8 @@
+[0] fail_json message: Error: interface aggi not found
+options:
+{
+    "iface": "aggi",
+    "option": "mtu",
+    "state": "present",
+    "value": "1350"
+}
diff --git a/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_set_aggi_and_eth0_mtu.exceptions.txt.license b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_set_aggi_and_eth0_mtu.exceptions.txt.license
new file mode 100644
--- /dev/null
+++ b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_set_aggi_and_eth0_mtu.exceptions.txt.license
@@ -0,0 +1,3 @@
+GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
+SPDX-License-Identifier: GPL-3.0-or-later
+SPDX-FileCopyrightText: Ansible Project
diff --git a/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_set_aggi_and_eth0_mtu.json b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_set_aggi_and_eth0_mtu.json
new file mode 100644
--- /dev/null
+++ b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_set_aggi_and_eth0_mtu.json
@@ -0,0 +1,21 @@
+{
+    "eth0": {
+        "address": "10.0.0.1",
+        "address_family": "inet",
+        "down": [],
+        "method": "static",
+        "mtu": "1500",
+        "netmask": "255.255.255.0",
+        "post-up": [],
+        "pre-up": [],
+        "up": []
+    },
+    "lo": {
+        "address_family": "inet",
+        "down": [],
+        "method": "loopback",
+        "post-up": [],
+        "pre-up": [],
+        "up": []
+    }
+}
diff --git a/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_set_aggi_and_eth0_mtu.json.license b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_set_aggi_and_eth0_mtu.json.license
new file mode 100644
--- /dev/null
+++ b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_set_aggi_and_eth0_mtu.json.license
@@ -0,0 +1,3 @@
+GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
+SPDX-License-Identifier: GPL-3.0-or-later
+SPDX-FileCopyrightText: Ansible Project
diff --git a/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_set_aggi_and_eth0_mtu.license b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_set_aggi_and_eth0_mtu.license
new file mode 100644
--- /dev/null
+++ b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_set_aggi_and_eth0_mtu.license
@@ -0,0 +1,3 @@
+GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
+SPDX-License-Identifier: GPL-3.0-or-later
+SPDX-FileCopyrightText: Ansible Project
diff --git a/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_set_aggi_slaves b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_set_aggi_slaves
new file mode 100644
--- /dev/null
+++ b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_set_aggi_slaves
@@ -0,0 +1,8 @@
+iface lo inet loopback
+auto lo
+
+auto eth0
+iface eth0 inet static                                                                                                                                   
+address 10.0.0.1
+netmask 255.255.255.0
+mtu 1500
diff --git a/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_set_aggi_slaves.exceptions.txt b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_set_aggi_slaves.exceptions.txt
new file mode 100644
--- /dev/null
+++ b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_set_aggi_slaves.exceptions.txt
@@ -0,0 +1,8 @@
+[0] fail_json message: Error: interface aggi not found
+options:
+{
+    "iface": "aggi",
+    "option": "slaves",
+    "state": "present",
+    "value": "int1  int3"
+}
diff --git a/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_set_aggi_slaves.exceptions.txt.license b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_set_aggi_slaves.exceptions.txt.license
new file mode 100644
--- /dev/null
+++ b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_set_aggi_slaves.exceptions.txt.license
@@ -0,0 +1,3 @@
+GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
+SPDX-License-Identifier: GPL-3.0-or-later
+SPDX-FileCopyrightText: Ansible Project
diff --git a/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_set_aggi_slaves.json b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_set_aggi_slaves.json
new file mode 100644
--- /dev/null
+++ b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_set_aggi_slaves.json
@@ -0,0 +1,21 @@
+{
+    "eth0": {
+        "address": "10.0.0.1",
+        "address_family": "inet",
+        "down": [],
+        "method": "static",
+        "mtu": "1500",
+        "netmask": "255.255.255.0",
+        "post-up": [],
+        "pre-up": [],
+        "up": []
+    },
+    "lo": {
+        "address_family": "inet",
+        "down": [],
+        "method": "loopback",
+        "post-up": [],
+        "pre-up": [],
+        "up": []
+    }
+}
diff --git a/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_set_aggi_slaves.json.license b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_set_aggi_slaves.json.license
new file mode 100644
--- /dev/null
+++ b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_set_aggi_slaves.json.license
@@ -0,0 +1,3 @@
+GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
+SPDX-License-Identifier: GPL-3.0-or-later
+SPDX-FileCopyrightText: Ansible Project
diff --git a/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_set_aggi_slaves.license b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_set_aggi_slaves.license
new file mode 100644
--- /dev/null
+++ b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/golden_output/no_leading_spaces_set_aggi_slaves.license
@@ -0,0 +1,3 @@
+GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
+SPDX-License-Identifier: GPL-3.0-or-later
+SPDX-FileCopyrightText: Ansible Project
diff --git a/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/input/no_leading_spaces b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/input/no_leading_spaces
new file mode 100644
--- /dev/null
+++ b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/input/no_leading_spaces
@@ -0,0 +1,8 @@
+iface lo inet loopback
+auto lo
+
+auto eth0
+iface eth0 inet static                                                                                                                                   
+address 10.0.0.1
+netmask 255.255.255.0
+mtu 1500
diff --git a/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/input/no_leading_spaces.license b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/input/no_leading_spaces.license
new file mode 100644
--- /dev/null
+++ b/tests/unit/plugins/modules/interfaces_file/interfaces_file_fixtures/input/no_leading_spaces.license
@@ -0,0 +1,3 @@
+GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
+SPDX-License-Identifier: GPL-3.0-or-later
+SPDX-FileCopyrightText: Ansible Project
diff --git a/tests/unit/plugins/modules/interfaces_file/test_interfaces_file.py b/tests/unit/plugins/modules/interfaces_file/test_interfaces_file.py
--- a/tests/unit/plugins/modules/interfaces_file/test_interfaces_file.py
+++ b/tests/unit/plugins/modules/interfaces_file/test_interfaces_file.py
@@ -44,6 +44,8 @@ def fail_json(self, msg):
 
 
 class TestInterfacesFileModule(unittest.TestCase):
+    unittest.TestCase.maxDiff = None
+
     def getTestFiles(self, include_filter=None, exclude_filter=None):
         flist = next(os.walk(fixture_path))[2]
         flist = [file for file in flist if not file.endswith('.license')]
@@ -73,10 +75,22 @@ def compareInterfacesLinesToFile(self, interfaces_lines, path, testname=None):
     def compareInterfacesToFile(self, ifaces, path, testname=None):
         if not testname:
             testname = "%s.%s.json" % (path, inspect.stack()[1][3])
-        self.compareStringWithFile(json.dumps(ifaces, sort_keys=True, indent=4, separators=(',', ': ')), testname)
+
+        testfilepath = os.path.join(golden_output_path, testname)
+        string = json.dumps(ifaces, sort_keys=True, indent=4, separators=(',', ': '))
+        if string and not string.endswith('\n'):
+            string += '\n'
+        goldenstring = string
+        goldenData = ifaces
+        if not os.path.isfile(testfilepath):
+            with io.open(testfilepath, 'wb') as f:
+                f.write(string.encode())
+        else:
+            with open(testfilepath, 'r') as goldenfile:
+                goldenData = json.load(goldenfile)
+        self.assertEqual(goldenData, ifaces)
 
     def compareStringWithFile(self, string, path):
-        # self.assertEqual("","_",msg=path)
         testfilepath = os.path.join(golden_output_path, path)
         if string and not string.endswith('\n'):
             string += '\n'
@@ -89,7 +103,7 @@ def compareStringWithFile(self, string, path):
             with open(testfilepath, 'r') as goldenfile:
                 goldenstring = goldenfile.read()
                 goldenfile.close()
-        self.assertEqual(string, goldenstring)
+        self.assertEqual(goldenstring, string)
 
     def test_no_changes(self):
         for testfile in self.getTestFiles():
@@ -224,7 +238,9 @@ def test_revert(self):
 
                     self.compareInterfacesLinesToFile(lines, testfile, "%s_%s" % (testfile, testname))
                     self.compareInterfacesToFile(ifaces, testfile, "%s_%s.json" % (testfile, testname))
-                    self.compareFileToBackup(path, backupp)
+                    if testfile not in ["no_leading_spaces"]:
+                        # skip if eth0 has MTU value
+                        self.compareFileToBackup(path, backupp)
 
     def test_change_method(self):
         testcases = {
@@ -269,6 +285,195 @@ def test_change_method(self):
                     # Restore backup
                     move(backupp, path)
 
+    def test_getValueFromLine(self):
+        testcases = [
+            {
+                "line": "  address   1.2.3.5",
+                "value": "1.2.3.5",
+            }
+        ]
+        for testcase in testcases:
+            value = interfaces_file.getValueFromLine(testcase["line"])
+            self.assertEqual(testcase["value"], value)
+
+    def test_get_interface_options(self):
+        testcases = {
+            "basic": {
+                "iface_lines": [
+                    {
+                        "address_family": "inet",
+                        "iface": "eno1",
+                        "line": "iface eno1 inet static",
+                        "line_type": "iface",
+                        "params": {
+                            "address": "",
+                            "address_family": "inet",
+                            "down": [],
+                            "gateway": "",
+                            "method": "static",
+                            "netmask": "",
+                            "post-up": [],
+                            "pre-up": [],
+                            "up": []
+                        }
+                    },
+                    {
+                        "address_family": "inet",
+                        "iface": "eno1",
+                        "line": "  address   1.2.3.5",
+                        "line_type": "option",
+                        "option": "address",
+                        "value": "1.2.3.5"
+                    },
+                    {
+                        "address_family": "inet",
+                        "iface": "eno1",
+                        "line": "  netmask   255.255.255.0",
+                        "line_type": "option",
+                        "option": "netmask",
+                        "value": "255.255.255.0"
+                    },
+                    {
+                        "address_family": "inet",
+                        "iface": "eno1",
+                        "line": "  gateway   1.2.3.1",
+                        "line_type": "option",
+                        "option": "gateway",
+                        "value": "1.2.3.1"
+                    }
+                ],
+                "iface_options": [
+                    {
+                        "address_family": "inet",
+                        "iface": "eno1",
+                        "line": "  address   1.2.3.5",
+                        "line_type": "option",
+                        "option": "address",
+                        "value": "1.2.3.5"
+                    },
+                    {
+                        "address_family": "inet",
+                        "iface": "eno1",
+                        "line": "  netmask   255.255.255.0",
+                        "line_type": "option",
+                        "option": "netmask",
+                        "value": "255.255.255.0"
+                    },
+                    {
+                        "address_family": "inet",
+                        "iface": "eno1",
+                        "line": "  gateway   1.2.3.1",
+                        "line_type": "option",
+                        "option": "gateway",
+                        "value": "1.2.3.1"
+                    }
+                ]
+            },
+        }
+
+        for testname in testcases.keys():
+            iface_options = interfaces_file.get_interface_options(testcases[testname]["iface_lines"])
+            self.assertEqual(testcases[testname]["iface_options"], iface_options)
+
+    def test_get_interface_options(self):
+        testcases = {
+            "select address": {
+                "iface_options": [
+                    {
+                        "address_family": "inet",
+                        "iface": "eno1",
+                        "line": "  address   1.2.3.5",
+                        "line_type": "option",
+                        "option": "address",
+                        "value": "1.2.3.5"
+                    },
+                    {
+                        "address_family": "inet",
+                        "iface": "eno1",
+                        "line": "  netmask   255.255.255.0",
+                        "line_type": "option",
+                        "option": "netmask",
+                        "value": "255.255.255.0"
+                    },
+                    {
+                        "address_family": "inet",
+                        "iface": "eno1",
+                        "line": "  gateway   1.2.3.1",
+                        "line_type": "option",
+                        "option": "gateway",
+                        "value": "1.2.3.1"
+                    }
+                ],
+                "target_options": [
+                    {
+                        "address_family": "inet",
+                        "iface": "eno1",
+                        "line": "  address   1.2.3.5",
+                        "line_type": "option",
+                        "option": "address",
+                        "value": "1.2.3.5"
+                    }
+                ],
+                "option": "address"
+            },
+        }
+
+        for testname in testcases.keys():
+            target_options = interfaces_file.get_target_options(testcases[testname]["iface_options"], testcases[testname]["option"])
+            self.assertEqual(testcases[testname]["target_options"], target_options)
+
+    def test_update_existing_option_line(self):
+        testcases = {
+            "update address": {
+                "target_option": {
+                    "address_family": "inet",
+                    "iface": "eno1",
+                    "line": "  address   1.2.3.5",
+                    "line_type": "option",
+                    "option": "address",
+                    "value": "1.2.3.5"
+                },
+                "value": "1.2.3.4",
+                "result": "  address   1.2.3.4",
+            },
+        }
+
+        for testname in testcases.keys():
+            updated = interfaces_file.update_existing_option_line(testcases[testname]["target_option"], testcases[testname]["value"])
+            self.assertEqual(testcases[testname]["result"], updated)
+
+    def test_predefined(self):
+        testcases = {
+            "idempotency": {
+                "source_lines": [
+                    "iface eno1 inet static",
+                    "  address   1.2.3.5",
+                    "  netmask   255.255.255.0",
+                    "  gateway   1.2.3.1",
+                ],
+                "input": {
+                    "iface": "eno1",
+                    "option": "address",
+                    "value": "1.2.3.5",
+                    'state': 'present',
+                },
+                "result_lines": [
+                    "iface eno1 inet static",
+                    "  address   1.2.3.5",
+                    "  netmask   255.255.255.0",
+                    "  gateway   1.2.3.1",
+                ],
+                "changed": False,
+            },
+        }
+
+        for testname in testcases.keys():
+            lines, ifaces = interfaces_file.read_interfaces_lines(module, testcases[testname]["source_lines"])
+            changed, lines = interfaces_file.set_interface_option(module, lines, testcases[testname]["input"]['iface'], testcases[testname]["input"]['option'],
+                                                                  testcases[testname]["input"]['value'], testcases[testname]["input"]['state'])
+            self.assertEqual(testcases[testname]["result_lines"], [d['line'] for d in lines if 'line' in d])
+            assert testcases[testname]['changed'] == changed
+
     def test_inet_inet6(self):
         testcases = {
             "change_ipv4": [