I'm trying to edit the 01-netcfg.yml
file with Ansible. Below is the file:
network:
version: 2
renderer: NetworkManager
ethernets:
enp0s31f6:
dhcp4: no
nameservers:
addresses: [8.8.8.8, 8.8.4.4]
enp12s0:
dhcp4: no
addresses:
- 192.168.0.1/24
enp8s0:
dhcp4: no
addresses:
- 192.168.1.1/24
enp9s0:
dhcp4: no
addresses:
- 192.168.16.1/24
I'm trying to change the dhcp4
value from no
to yes
under enp0s31f6
only.
- lineinfile:
path: /etc/netplan/01-netcfg.yaml
regexp: ' dhcp4: no'
line: ' dhcp4: yes'
insertbefore: ' nameservers:'
firstmatch: True
state: present
When I run the above code, it is modifying the dhcp4
under enp0s31f6
and also the dhcp4
under enp12s0
. I tried insertafter as well, but same result.
Q: "Change the dhcp4 value from no to yes under enp0s31f6 only."
Given the file for testing
shell> cat /tmp/netplan/01-netcfg.yml
network:
version: 2
renderer: NetworkManager
ethernets:
enp0s31f6:
dhcp4: no
nameservers:
addresses: [8.8.8.8, 8.8.4.4]
enp12s0:
dhcp4: no
addresses:
- 192.168.0.1/24
enp8s0:
dhcp4: no
addresses:
- 192.168.1.1/24
enp9s0:
dhcp4: no
addresses:
- 192.168.16.1/24
A: Do not use the module lineinfile (1) in this use_case. The module replace (2) might be a better option. The best option is to fetch, update, and copy the file (3).
Generally, the module lineinfile is not able to update any neplane interface's parameter. In the particular case of the given file, the task below does the job
- lineinfile:
path: /tmp/netplan/01-netcfg.yml
regexp: '^(\s+)dhcp4:\s+.*$'
backrefs: true
line: '\1dhcp4: yes'
firstmatch: true
Running the play with the options --check --diff gives
TASK [lineinfile] *****************************************************************************
--- before: /tmp/netplan/01-netcfg.yml (content)
+++ after: /tmp/netplan/01-netcfg.yml (content)
@@ -3,7 +3,7 @@
renderer: NetworkManager
ethernets:
enp0s31f6:
- dhcp4: no
+ dhcp4: yes
nameservers:
addresses: [8.8.8.8, 8.8.4.4]
enp0s31f6:
This works because the interface enp0s31f6
is first in the ethernets
keys.
Notes:
backrefs: true
. Put the leading whitespace into the group \1
firstmatch: true
. The default false would match the last dhcp4
in the interface enp9s0
. Quoting from regexp: "Only the last line found will be replaced."This doesn't work if the interface enp0s31f6
is not the first in the dictionary ethernets
. For example,
shell> cat /tmp/netplan/01-netcfg.yml
network:
version: 2
renderer: NetworkManager
ethernets:
eth01:
dhcp4: no
nameservers:
addresses: [8.8.8.8, 8.8.4.4]
enp0s31f6:
dhcp4: no
nameservers:
addresses: [8.8.8.8, 8.8.4.4]
The task below, even with the option insertafter: enp0s31f6
- lineinfile:
path: /tmp/netplan/01-netcfg.yml
regexp: '^(\s+)dhcp4:\s+.*$'
backrefs: true
line: '\1dhcp4: yes'
insertafter: enp0s31f6
firstmatch: true
gives
TASK [lineinfile] *****************************************************************************
--- before: /tmp/netplan/01-netcfg.yml (content)
+++ after: /tmp/netplan/01-netcfg.yml (content)
@@ -3,7 +3,7 @@
renderer: NetworkManager
ethernets:
eth01:
- dhcp4: no
+ dhcp4: yes
nameservers:
addresses: [8.8.8.8, 8.8.4.4]
enp0s31f6:
because insertafter
doesn't work if regexp
is matched. Quoting from regexp:
If the regular expression is not matched, the line will be added to the file in keeping with
insertbefore
orinsertafter
settings.
If you remove the option regexp
the option backrefs
can't work, and the regexp group \1
can't be used in line
. Then, the task below
- lineinfile:
path: /tmp/netplan/01-netcfg.yml
line: ' dhcp4: yes'
insertafter: enp0s31f6
firstmatch: true
doesn't replace the existing parameter dhcp4: no
. Instead, a new one will be added
TASK [lineinfile] *****************************************************************************
--- before: /tmp/netplan/01-netcfg.yml (content)
+++ after: /tmp/netplan/01-netcfg.yml (content)
@@ -7,6 +7,7 @@
nameservers:
addresses: [8.8.8.8, 8.8.4.4]
enp0s31f6:
+ dhcp4: yes
dhcp4: no
nameservers:
addresses: [8.8.8.8, 8.8.4.4]
The module replace seems to be a better option. The task below
- replace:
path: /tmp/netplan/01-netcfg.yml
after: enp0s31f6
before: enp12s0
regexp: 'dhcp4:\s+.*\n'
replace: 'dhcp4: yes\n'
does the job
TASK [replace] ********************************************************************************
--- before: /tmp/netplan/01-netcfg.yml
+++ after: /tmp/netplan/01-netcfg.yml
@@ -3,7 +3,7 @@
renderer: NetworkManager
ethernets:
enp0s31f6:
- dhcp4: no
+ dhcp4: yes
nameservers:
addresses: [8.8.8.8, 8.8.4.4]
enp12s0:
The problem is that you have to provide the option before: enp12s0
. There might be another interface or none at all.
<TBD: get the value of before>
Use the module fetch to fetch the file(s) and the filter combine to update the configuration. Then, you can synchronize the file(s) with the remote host(s). This procedure provides a robust, structured, and easily extensible framework for updating multiple netplan configuration files on multiple remote hosts in parallel.
Declare the path to netplan
netplan_dir: /tmp/netplan
and the dictionary with the updates
netplan_update:
01-netcfg.yml:
network:
ethernets:
enp0s31f6:
dhcp4: yes
Running on the localhost for testing, fetch the file(s)
- fetch:
dest: /tmp/fetch
src: "{{ netplan_dir }}/{{ item }}"
loop: "{{ netplan_update.keys()|list }}"
Take a look at the fetched file(s)
shell> tree /tmp/fetch/
/tmp/fetch/
└── localhost
└── tmp
└── netplan
└── 01-netcfg.yml
3 directories, 1 file
Read the configuration into the dictionary netplan_orig
- set_fact:
netplan_orig: "{{ netplan_orig|d({})|combine({item: conf}) }}"
loop: "{{ netplan_update.keys()|list }}"
vars:
file: "/tmp/fetch/{{ inventory_hostname }}{{ netplan_dir }}/{{ item }}"
conf: "{{ lookup('file', file)|from_yaml }}"
gives
netplan_orig:
01-netcfg.yml:
network:
ethernets:
enp0s31f6:
dhcp4: false
nameservers:
addresses:
- 8.8.8.8
- 8.8.4.4
enp12s0:
addresses:
- 192.168.0.1/24
dhcp4: false
enp8s0:
addresses:
- 192.168.1.1/24
dhcp4: false
enp9s0:
addresses:
- 192.168.16.1/24
dhcp4: false
renderer: NetworkManager
version: 2
Update the configuration. Declare the combination of the dictionaries
netplan_conf: "{{ netplan_orig|
combine(netplan_update, recursive=true) }}"
gives
netplan_conf:
01-netcfg.yml:
network:
ethernets:
enp0s31f6:
dhcp4: true
nameservers:
addresses:
- 8.8.8.8
- 8.8.4.4
enp12s0:
addresses:
- 192.168.0.1/24
dhcp4: false
enp8s0:
addresses:
- 192.168.1.1/24
dhcp4: false
enp9s0:
addresses:
- 192.168.16.1/24
dhcp4: false
renderer: NetworkManager
version: 2
Update the fetched files
- copy:
dest: "/tmp/fetch/{{ inventory_hostname }}{{ netplan_dir }}/{{ item.key }}"
content: "{{ item.value|to_nice_yaml(indent=2) }}"
loop: "{{ netplan_conf|dict2items }}"
delegate_to: localhost
Take a look at the content of the file
shell> cat /tmp/fetch/localhost/tmp/netplan/01-netcfg.yml
network:
ethernets:
enp0s31f6:
dhcp4: true
nameservers:
addresses:
- 8.8.8.8
- 8.8.4.4
enp12s0:
addresses:
- 192.168.0.1/24
dhcp4: false
enp8s0:
addresses:
- 192.168.1.1/24
dhcp4: false
enp9s0:
addresses:
- 192.168.16.1/24
dhcp4: false
renderer: NetworkManager
version: 2
Synchronize the file(s)
- synchronize:
src: "/tmp/fetch/{{ inventory_hostname }}{{ netplan_dir }}/{{ item }}"
dest: "{{ netplan_dir }}/{{ item }}"
loop: "{{ netplan_update.keys()|list }}"
# notify: netplan apply
Uncomment the notify directive to apply the configuration and create the handler
handlers:
- name: netplan apply
command: netplan apply
Example of a complete playbook for testing
- hosts: localhost
vars:
netplan_dir: /tmp/netplan
netplan_update:
01-netcfg.yml:
network:
ethernets:
enp0s31f6:
dhcp4: yes
netplan_conf: "{{ netplan_orig|
combine(netplan_update, recursive=true) }}"
tasks:
- fetch:
dest: /tmp/fetch
src: "{{ netplan_dir }}/{{ item }}"
loop: "{{ netplan_update.keys()|list }}"
tags: netplan_fetch
- set_fact:
netplan_orig: "{{ netplan_orig|d({})|combine({item: conf}) }}"
loop: "{{ netplan_update.keys()|list }}"
vars:
file: "/tmp/fetch/{{ inventory_hostname }}{{ netplan_dir }}/{{ item }}"
conf: "{{ lookup('file', file)|from_yaml }}"
tags: [netplan_read, netplan_update]
- debug:
var: netplan_orig
tags: netplan_read
when: debug|d(false)|bool
- debug:
var: netplan_conf
when: debug|d(false)|bool
tags: netplan_read
- copy:
dest: "/tmp/fetch/{{ inventory_hostname }}{{ netplan_dir }}/{{ item.key }}"
content: "{{ item.value|to_nice_yaml(indent=2) }}"
loop: "{{ netplan_conf|dict2items }}"
delegate_to: localhost
tags: netplan_update
- synchronize:
src: "/tmp/fetch/{{ inventory_hostname }}{{ netplan_dir }}/{{ item }}"
dest: "{{ netplan_dir }}/{{ item }}"
loop: "{{ netplan_update.keys()|list }}"
# notify: netplan apply
tags: netplan_synchronize
handlers:
- name: netplan apply
command: netplan apply