Repository: ansible-collections/community.general

Issue:
Updating network interface method ignores specified address family
### Summary

I am trying to update the _method_ of a network interface in `/etc/network/interfaces` while specifying `address_family`, using the `community.general.interfaces_file` module.

It seems that the interfaces are not properly filtered by address family in doing so. As a result, all entries of that interface have their _method_ updated.

### Issue Type

Bug Report

### Component Name

interfaces_file

### Ansible Version

```console (paste below)
$ ansible --version
ansible [core 2.16.0]
  config file = None
  configured module search path = ['/home/user/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
  ansible python module location = /home/user/.local/pipx/venvs/ansible/lib/python3.11/site-packages/ansible
  ansible collection location = /home/user/.ansible/collections:/usr/share/ansible/collections
  executable location = /home/user/.local/bin/ansible
  python version = 3.11.2 (main, Mar 13 2023, 12:18:29) [GCC 12.2.0] (/home/user/.local/pipx/venvs/ansible/bin/python)
  jinja version = 3.1.2
  libyaml = True
```

### Community.general Version

```console (paste below)
$ ansible-galaxy collection list community.general
# /home/user/.local/pipx/venvs/ansible/lib/python3.11/site-packages/ansible_collections
Collection        Version
----------------- -------
community.general 8.0.2
```


### Configuration

```console (paste below)
$ ansible-config dump --only-changed
CALLBACKS_ENABLED(env: ANSIBLE_CALLBACKS_ENABLED) = ['profile_tasks']
CONFIG_FILE() = None
DEFAULT_VAULT_PASSWORD_FILE(env: ANSIBLE_VAULT_PASSWORD_FILE) = /home/user/.vault-personal
RETRY_FILES_ENABLED(env: ANSIBLE_RETRY_FILES_ENABLED) = False
```


### OS / Environment

Both host and target OS is Debian 12.

### Steps to Reproduce

<!--- Paste example playbooks or commands between quotes below -->
```yaml (paste below)
---

- name: Configure IPv6 interface
  community.general.interfaces_file:
    iface: "{{ ansible_default_ipv4.alias }}"
    address_family: "inet6"
    option: "{{ item.option }}"
    value: "{{ item.value }}"
  loop:
    - option: "method"
      value: "static"
    - option: "address"
      value: "{{ static_ipv6_address }}/48"
  loop_control:
    label: "{{ item.option }}"
```


### Expected Results

Given the following entries for an interface:

```
iface ens3 inet dhcp
iface ens3 inet6 auto
```

I expect the playbook above to update them to:

```
iface ens3 inet dhcp
iface ens3 inet6 static
    address fd00:1234:5678:1::1/48
```

### Actual Results

Instead, it's updated to the following result:

```
iface ens3 inet static
iface ens3 inet6 static
    address fd00:1234:5678:1::1/48
```

The `inet` entry also has its _method_ updated to `static`, whereas it should stay `dhcp`.

### Code of Conduct

- [X] I agree to follow the Ansible Code of Conduct


Discussion:
I think the problem is here:
```
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
        for ln in lines:
            if ln.get('line_type', '') == 'iface' and ln.get('iface', '') == iface and value != ln.get('params', {}).get('method', ''):
                changed = True
                ln['line'] = re.sub(ln.get('params', {}).get('method', '') + '$', value, ln.get('line'))
                ln['params']['method'] = value
        return changed, lines
    [...]
```
As opposed to for other `option`s, it only matches for `iface`, but not for `address_family`.
cc @hryamzik @obourdon
[click here for bot help](https://github.com/ansible/ansibullbot/blob/devel/ISSUE_HELP.md)
<!--- boilerplate: notify --->
I'd update test cases to catch this and then check how to fix the function.
I already fixed it.
#7612
Thanks a lot for picking this up so quickly, @felixfontein! Very much appreciated. 🙏🏻

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
@@ -336,6 +336,8 @@ def addOptionAfterLine(option, value, iface, lines, last_line_dict, iface_option
         changed = False
         for ln in lines:
             if ln.get('line_type', '') == 'iface' and ln.get('iface', '') == iface and value != ln.get('params', {}).get('method', ''):
+                if address_family is not None and ln.get('address_family') != address_family:
+                    continue
                 changed = True
                 ln['line'] = re.sub(ln.get('params', {}).get('method', '') + '$', value, ln.get('line'))
                 ln['params']['method'] = value


Test patch:
diff --git a/tests/integration/targets/interfaces_file/tasks/main.yml b/tests/integration/targets/interfaces_file/tasks/main.yml
--- a/tests/integration/targets/interfaces_file/tasks/main.yml
+++ b/tests/integration/targets/interfaces_file/tasks/main.yml
@@ -7,6 +7,7 @@
   set_fact:
     interfaces_testfile: '{{ remote_tmp_dir }}/interfaces'
     interfaces_testfile_3841: '{{ remote_tmp_dir }}/interfaces_3841'
+    interfaces_testfile_7610: '{{ remote_tmp_dir }}/interfaces_7610'
 
 - name: Copy interfaces file
   copy:
@@ -65,3 +66,60 @@
     that:
       - ifile_3841_a is changed
       - ifile_3841_b is not changed
+
+- name: 7610 - create file
+  copy:
+    dest: '{{ interfaces_testfile_7610 }}'
+    content: |
+      iface ens3 inet dhcp
+      iface ens3 inet6 auto
+
+- name: 7610 - modify file
+  interfaces_file:
+    dest: '{{ interfaces_testfile_7610 }}'
+    iface: ens3
+    address_family: "inet6"
+    option: "{{ item.option }}"
+    value: "{{ item.value }}"
+  loop:
+    - option: "method"
+      value: "static"
+    - option: "address"
+      value: "1:2::3/48"
+
+- name: 7610 - read file
+  slurp:
+    src: '{{ interfaces_testfile_7610 }}'
+  register: content_7610
+
+- name: 7610 - check assertions
+  assert:
+    that:
+      - content_7610.content | b64decode == expected_content
+  vars:
+    expected_content: |
+      iface ens3 inet dhcp
+      iface ens3 inet6 static
+          address 1:2::3/48
+
+- name: 7610 - modify file again
+  interfaces_file:
+    dest: '{{ interfaces_testfile_7610 }}'
+    iface: ens3
+    option: method
+    value: foobar
+
+- name: 7610 - read file
+  slurp:
+    src: '{{ interfaces_testfile_7610 }}'
+  register: content_7610
+
+- name: 7610 - check assertions
+  assert:
+    that:
+      - content_7610.content | b64decode == expected_content
+  vars:
+    expected_content: |
+      iface ens3 inet foobar
+      iface ens3 inet6 foobar
+          address 1:2::3/48