Ansible: Modify cmdline.txt on Raspberry Pi - ansible

I am modifying /boot/cmdline.txt to add container features to a Raspberry Pi, so I need to add cgroup_enable=cpuset cgroup_memory=1 cgroup_enable=memory into the file, within the same line.
I am trying to do it with the lineinfile module without much success:
- hosts: mypi
become: yes
tasks:
- name: Enable container features
lineinfile:
path: /boot/cmdline.txt
regex: " cgroup_enable=cpuset cgroup_memory=1 cgroup_enable=memory"
line: " cgroup_enable=cpuset cgroup_memory=1 cgroup_enable=memory"
insertafter: EOF
state: present
I have been trying modifying the insertafter to BOF, using insertbefore too, using a regex to match the last word... But it ends up adding a carriage return. I have been unable to find some way to not add a new line.

As Vladimir pointed out, Jack's answer unfortunately is not sufficient for empty files and also fails if the desired argument already exists at the beginning of the line.
The following suggested solution should address those issues. In particular, it is supposed to
support empty files,
support existing arguments at any position within the string,
be robust even with multi-line files (just in case...),
be idempotent, and
optionally update existing keys with the desired value.
# cmdline.yml
- name: read cmdline.txt
become: true
slurp: "src={{ cmdline_txt_path }}"
register: result_cmdline
- name: generate regular expression for existing arguments
set_fact:
regex_existing: '{{ "\b" + key|string + "=" + ("[\w]*" if update else value|string) + "\b" }}'
key_value_pair: '{{ key|string + "=" + value|string }}'
- name: generate regular expression for new arguments
set_fact:
regex_add_missing: '{{ "^((?!(?:.|\n)*" + regex_existing + ")((?:.|\n)*))$" }}'
- name: update cmdline.txt
become: true
copy:
content: '{{ result_cmdline.content
| b64decode
| regex_replace(regex_existing, key_value_pair)
| regex_replace(regex_add_missing, key_value_pair + " \1")
}}'
dest: "{{ cmdline_txt_path }}"
Usage:
- set_fact:
cmdline_txt_path: /boot/cmdline.txt
- include_tasks: cmdline.yml
vars:
key: cgroup_enable
value: memory
update: false
# will add the argument if the key-value-pair doesn't exist
- include_tasks: cmdline.yml
vars:
key: cgroup_enable
value: cpu
update: false
- include_tasks: cmdline.yml
vars:
key: cgroup_memory
value: 1
update: true
# will replace the value of the first matching key, if found;
# will add it if it's not found
However, I might have missed some edge cases - please let me know if you find any issues.

Since you only have the one line in the file, you can do that with either replace or lineinfile. Here is the replace version:
- name: Enable container features
replace:
path: cmdline.txt
regexp: '^([\w](?!.*\b{{ item }}\b).*)$'
replace: '\1 {{ item }}'
with_items:
- "cgroup_enable=cpuset"
- "cgroup_memory=1"
- "cgroup_enable=memory"
Stole the answer from here

I followed all of the different strategies laid out above, but in the end, I wanted something simple, as this is my first playbook, and I need to understand it now, and when I pick it up again later,.
My cmdline.txt contained multiple lines:
cat /boot/cmdline.txt -E
console=serial0,115200 console=tty1 rootfstype=ext4 fsck.repair=yes rootwait cgroup_enable=memory cgroup_enable=cpuset cgroup_memory=1$
dtoverlay=pi3-disable-bt$
dtoverlay=pi3-disable-wifi$
So, the approach I was looking for:
Would ignore the other configurations in the cmdline.txt
Would only add the add a specific key=value if it was missing
It had to be idempotent
I settled on a simple regex to decide if this was the row I wanted to edit:
If the row contaied console= (as this is the row I'm after)
AND.. If the row does not contain cgroup_memory=1
- name: Adding cgroup_enable=memory to boot parameters for k3s
lineinfile:
path: /boot/cmdline.txt
state: present
regexp: '^((?!.*cgroup_enable=memory).*console.*)$'
line: '\1 cgroup_enable=memory'
backrefs: yes
notify: reboot
- name: Adding cgroup_enable=cpuset to boot parameters for K3s
lineinfile:
path: /boot/cmdline.txt
state: present
regexp: '^((?!.*cgroup_enable=cpuset).*console.*)$'
line: '\1 cgroup_enable=cpuset'
backrefs: yes
notify: reboot
- name: Adding cgroup_memory=1 to boot parameters for K3s
lineinfile:
path: /boot/cmdline.txt
state: present
regexp: '^((?!.*cgroup_memory=1).*console.*)$'
line: '\1 cgroup_memory=1'
backrefs: yes
notify: reboot
And at some point in future, I'll probably condense all three of these into a single loop task. But not today.

Q: "Ansible lineinfile module: Do not add new line. Find some way to not add a new line."
A: It's not possible. New line will be always added by module lineinfile. See source for example
b_lines.insert(index[1], b_line + b_linesep)
This is how a new line is added. Such additions will be terminated with b_linesep. See how the variable is defined
b_linesep = to_bytes(os.linesep, errors='surrogate_or_strict')
The os.linesep is used when you want to iterate through the lines of a text file. The internal scanner recognizes the os.linesep and replaces it with a single "\n".
See What is os.linesep for?.
The task with the module replace doesn't solve this problem either. Neither it creates the line without a newline, nor it modifies existing one this way. In addition to this it's not idempotent.
- name: Enable container features
replace:
path: cmdline.txt
regexp: '^([\w](?!.*\b{{ item }}\b).*)$'
replace: '\1 {{ item }}'
loop:
- "cgroup_enable=cpuset"
- "cgroup_memory=1"
- "cgroup_enable=memory"
It will do nothing if the file is empty
TASK [Enable container features]
ok: [localhost] => (item=cgroup_enable=cpuset)
ok: [localhost] => (item=cgroup_memory=1)
ok: [localhost] => (item=cgroup_enable=memory)
If the line is present in the file this task will change it
shell> cat cmdline.txt
cgroup_enable=cpuset cgroup_memory=1 cgroup_enable=memory
ASK [Enable container features] *****************************************
ok: [localhost] => (item=cgroup_enable=cpuset)
--- before: cmdline.txt
+++ after: cmdline.txt
## -1 +1 ##
-cgroup_memory=1 cgroup_enable=memory cgroup_enable=cpuset
+cgroup_memory=1 cgroup_enable=memory cgroup_enable=cpuset cgroup_memory=1

Related

How to add content/string containing special character in file using ansible playbook?

Problem Statement:
I want to update the file /app/opt/MicroStrategy/MSIReg.reg_bkp with content "DSNotUseUnicodeForPT"=dword:00000001 under the line matching [HKEY_LOCAL_MACHINE\SOFTWARE\MicroStrategy\DSS Server\Castor].
I have created the below playbook task to use three variables:
- name: Add the entry as per our requirement
ansible.builtin.lineinfile:
path: "{{ FILE_PATH_TO_CHANGE }}"
insertafter: "{{ STRING_TO_MATCH }}"
line: "{{ STRING_TO_ADD }}"
owner: root
group: root
mode: '0664'
state: present
The above variables I am passing as extra variables as below but the file is not getting updated. Can someone please help me with possible reasons?
FILE_PATH_TO_CHANGE=/app/opt/MicroStrategy/MSIReg.reg_bkp
STRING_TO_MATCH=[HKEY_LOCAL_MACHINE\SOFTWARE\MicroStrategy\DSS Server\Castor]
STRING_TO_ADD="DSNotUseUnicodeForPT"=dword:00000001
Command ran is: ansible-playbook edit_file.yml -u username -e "FILE_PATH_TO_CHANGE=/app/opt/MicroStrategy/MSIReg.reg_bkp STRING_TO_ADD="DSNotUseUnicodeForPT"=dword:00000001" STRING_TO_MATCH=[HKEY_LOCAL_MACHINE\SOFTWARE\MicroStrategy\DSS Server\Castor] --ask-pass
Update: Contain of /app/opt/MicroStrategy/MSIReg.reg_bkp
[HKEY_LOCAL_MACHINE\SOFTWARE\MicroStrategy\DSS Server\Castor]
"ClusterMembers"="hostnames"
"ClusterName"="abcd"
"DSHostName"=""
"DSMaxConn"=dword:000003e8
"DSNumAggregateThreads"=dword:00000004
"DSNumDecompressThreads"=dword:00000001
"DSNumDeserializeThreads"=dword:00000004
"DSNumReceiverThreads"=dword:00000001
"DSPort"=dword:00007621
"HomePath"="/app/opt/MicroStrategy/IntelligenceServer"
"IgnoreAllExceptions"=dword:00000000
"MaintenanceClusterMembers"="hostnames"
"MaintenanceModeEnabled"=dword:00000001
"ProcessAffinity"=""
"UseServerOSLocaleinFallback"="0"
[HKEY_LOCAL_MACHINE\SOFTWARE\MicroStrategy\DSS Server\Instances]^M
"DefaultInstanceName"="CastorServer"
[HKEY_LOCAL_MACHINE\SOFTWARE\MicroStrategy\DSS Server\Instances\CastorServer]
"AsymmetricClustering"=dword:00000001^M
"ClusteringInUse"=dword:00000001^M
"MetaDataDBEncryption"="UTF8"^M
"MetaDataDatabaseVersion"="-1"^M
"MetaDataODBCDriverLibraryName"="<MySQL_ODBC_DIR>/libmyodbc8w.so"^M
"MetaDataODBCDriverVersion"="3.52"
"NumberOfNodesInCluster"=dword:00000002
The challenge with passing [HKEY_LOCAL_MACHINE...] was that it was being treated like a List so I had to make this variable value to just HKEY_LOCAL_MACHINE without []. This works but I am not sure if you like this modification for your insertlineafter value.
Here is the playbook
---
- name: SO test
hosts: localhost
connection: local
vars:
FILE_PATH_TO_CHANGE: /home/user100/Documents/ansible/testso3.txt
STRING_TO_MATCH: HKEY_LOCAL_MACHINE
STRING_TO_ADD: '"DSNotUseUnicodeForPT"=dword:00000001"'
tasks:
- name: write in file
lineinfile:
path: "{{ FILE_PATH_TO_CHANGE }}"
insertafter: "^.*{{ STRING_TO_MATCH }}.*$"
line: "{{ STRING_TO_ADD }}"
mode: '0664'
state: present
and the file before the play
[user100#zini-1880 ansible]$ cat testso3.txt
[HKEY_LOCAL_MACHINE\SOFTWARE\MicroStrategy\DSS Server\Castor]
"ClusterMembers"="hostnames"
"ClusterName"="abcd"
"DSHostName"=""
"DSMaxConn"=dword:000003e8
"DSNumAggregateThreads"=dword:00000004
"DSNumDecompressThreads"=dword:00000001
"DSNumDeserializeThreads"=dword:00000004
"DSNumReceiverThreads"=dword:00000001
"DSPort"=dword:00007621
"HomePath"="/app/opt/MicroStrategy/IntelligenceServer"
"IgnoreAllExceptions"=dword:00000000
"MaintenanceClusterMembers"="hostnames"
"MaintenanceModeEnabled"=dword:00000001
"ProcessAffinity"=""
"UseServerOSLocaleinFallback"="0"
[user100#zini-1880 ansible]$
and after the play
[user100#zini-1880 ansible]$ cat testso3.txt
[HKEY_LOCAL_MACHINE\SOFTWARE\MicroStrategy\DSS Server\Castor]
"DSNotUseUnicodeForPT"=dword:00000001"
"ClusterMembers"="hostnames"
"ClusterName"="abcd"
"DSHostName"=""
"DSMaxConn"=dword:000003e8
"DSNumAggregateThreads"=dword:00000004
"DSNumDecompressThreads"=dword:00000001
"DSNumDeserializeThreads"=dword:00000004
"DSNumReceiverThreads"=dword:00000001
"DSPort"=dword:00007621
"HomePath"="/app/opt/MicroStrategy/IntelligenceServer"
"IgnoreAllExceptions"=dword:00000000
"MaintenanceClusterMembers"="hostnames"
"MaintenanceModeEnabled"=dword:00000001
"ProcessAffinity"=""
"UseServerOSLocaleinFallback"="0"
[user100#zini-1880 ansible]$
if you want to not have the vars in your play-book, you can remove them here and pass as command line arguments as you did in your original post.

Add a string inside a configuration file through ansible

I am in need of adding a new string to one of the configuration file. I have to add say "mycustomimage" with a "," in "images =". So in short, my required output is images= previousimage,mycustomimage
View of mycnf.conf
id=1
images=previousimage
For this, I tried this code
---
- hosts: test_server
- name: Add new string after "," in images
lineinfile:
path: /home/mycnf.conf
regexp: 'images='
insertafter: '^,'
line: mycustomimage
Expected output
id= 1
images=previousimage,mycustomimage
But its not working for me. Any thoughts?
Thanks in advance!
Sid
This will help
- name: replace line
lineinfile:
path: myfile.txt
regexp: "^image="
line: 'image=previousimage,customimage'
From your example
You know the final line you want to have in the file is images=previousimage,mycustomimage
You want to add this line in place of an already existing image=.* line if it exists and does not match the final one.
The following will do the job
- name: Replace line if needed
lineinfile:
path: /home/mycnf.conf
regex: images=.*
line: images=previousimage,mycustomimage
Note: If for any reason there is no matching line in your file for the regex, the line will be added at the end of the file.
Assuming that previousimage is not known you can do two things:
1.Get this line using grep, register to a variable and add the line
- name: get line
shell: grep "^image=" /config/file.something
register: current_image
- name: update image
lineinfile:
path: /config/file.something
regexp: '^image='
line: "{{ current_image.stdout }},{{ new_image | default('customimage') }}"
2.Create a template for this configuration file and render it every time the playbook run and a change is detected:
- set_fact:
images: <list of images retrieved by lookup or static>
- name: update config.something
template:
src: my_template.j2
dest: /config/file.something
The template will look like this:
id={{ id }}
images={{ images | join(",") }}
We'll assume you don't know what previousimage is, that you may have several previous images, and you want to append mycustomimage, and you want the playbook to be idempotent:
---
- hosts: all
connection: ssh
become: no
gather_facts: no
vars:
image_name: mycustomimage
tasks:
- lineinfile:
path: testfile.txt
regexp: '^images=(.*(?<!{{ image_name }}))'
line: '\1,{{ image_name }}'
backrefs: yes
So let's explain the regexp: ^images= you can figure out yourself!
The first parenthesis starts the backref block, and it's going to suck up everything to the end of the line: .*
Then, it looks back at what it just sucked in, and makes sure {{ image_name }} is not at the end: (?<!{{ image_name }})
Finally, we close the backref block with ).

Remove the users from /etc/ssh/sshd_config with Ansible

I am trying to remove users added to the AllowGroups line in the /etc/ssh/sshd_conf file using the following after the comments from #imjoseangel.
The current line is
AllowGroups devops1 devops2 devops3 user01 user02 user03
The desired line is
AllowGroups devops1 devops2 user02 user03
The play is
name: Remove User from AllowGroups (opening dash removed)
replace:
regexp: "(^AllowGroups)\\s.*"
backup: True
path: /etc/ssh/sshd_config.bak
replace: "\\1 {{ item }} "
with_items:
- devops3
- user01
After the play is complete I am getting the following
"AllowGroups user01"
The whole is replaced by last user in the "item".
This solution worked.
tasks:
- name: Remove User from AllowGroup and AllowUsers
replace:
regexp: "{{ item }}"
backup: True
path: /etc/ssh/sshd_config.bak
replace: "\b"
with_items:
- devops1
- user03

Ansible uncomment line and change string value

I've got a line in a config file as follows:
# value = False
I know you can use lineinfile to remove the '#' as follows:
- name: Uncomment parameters
lineinfile:
dest: app.conf
regexp: (?i)^\s*#\s*({{ item }}.*)
line: \1
backrefs: yes
with_items:
- value
I was wonder if there was a way to also change the 'False' to 'True' in the same task or would that require another task? (using replace most likely?)
Sure, unless there is some other logic you want added?
- name: Uncomment parameters
lineinfile:
dest: app.conf
regexp: (?i)^\s*#\s*{{ item }}.*
line: "{{ item }} = True"
backrefs: yes
with_items:
- value

Ansible: Insert line if not exists

I'm trying insert a line in a property file using ansible.
I want to add some property if it does not exist, but not replace it if such property already exists in the file.
I add to my ansible role
- name: add couchbase host to properties
lineinfile: dest=/database.properties regexp="^couchbase.host" line="couchbase.host=127.0.0.1"
But this replaces the property value back to 127.0.0.1 if it exists already in the file.
What I'm doing wrong?
The lineinfile module ensures the line as defined in line is present in the file and the line is identified by your regexp. So no matter what value your setting already has, it will be overridden by your new line.
If you don't want to override the line you first need to test the content and then apply that condition to the lineinfile module. There is no module for testing the content of a file so you probably need to run grep with a shell command and check the .stdout for content. Something like this (untested):
- name: Test for line
shell: grep -c "^couchbase.host" /database.properties || true
register: test_grep
And then apply the condition to your lineinfile task:
- name: add couchbase host to properties
lineinfile:
dest: /database.properties
line: couchbase.host=127.0.0.1
when: test_grep.stdout == "0"
The regexp then can be removed since you already made sure the line doesn't exist so it never would match.
But maybe you're doing things back to front. Where does that line in the file come from? When you manage your system with Ansible there should be no other mechanisms in place which interfere with the same config files. Maybe you can work around this by adding a default value to your role?
This is possible by simply using lineinfile and check_mode:
- name: Check if couchbase.host is already defined
lineinfile:
state: absent
path: "/database.properties"
regexp: "^couchbase.host="
check_mode: true
changed_when: false # This just makes things look prettier in the logs
register: check
- name: Define couchbase.host if undefined
lineinfile:
state: present
path: "/database.properties"
line: "couchbase.host=127.0.0.1"
when: check.found == 0
This is the only way I was able to get this to work.
- name: checking for host
shell: cat /database.properties | grep couchbase.host | wc -l
register: test_grep
- debug: msg="{{test_grep.stdout}}"
- name: adding license server
lineinfile: dest=/database.properties line="couchbase.host=127.0.0.1"
when: test_grep.stdout == "0"
By a long way of "Trials and errors" I come to this:
- name: check existence of line in the target file
command: grep -Fxq "ip addr add {{ item }}/32 dev lo label lo:{{ app | default('app') }}" /etc/rc.local
changed_when: false
failed_when: false
register: ip_test
with_items:
- "{{ list_of_ips }}"
- name: add autostart command
lineinfile: dest=/etc/rc.local
line="ip addr add {{ item.item }}/32 dev lo label lo:{{ app | default('app') }}"
insertbefore="exit 0"
state=present
when: item.rc == 1
with_items:
- "{{ ip_test.results }}"
Looks like it does not work if you use backrefs.
Following sample does not add a line
- name: add hosts file entry
lineinfile:
path: "/etc/hosts"
regexp: "foohost"
line: "10.10.10.10 foohost"
state: present
backrefs: yes
After removing backrefs I got my line added to the file
- name: add hosts file entry
lineinfile:
path: "/etc/hosts"
regexp: "foohost"
line: "10.10.10.10 foohost"
state: present
Same idea as presented here : https://stackoverflow.com/a/40890850/7231194
Steps are:
Try to replace the line.
If replace mod change it, restore
If replace mod doesn't change, add the line
Example
# Vars
- name: Set parameters
set_fact:
ipAddress : "127.0.0.1"
lineSearched : "couchbase.host={{ ipAddress }}"
lineModified : "couchbase.host={{ ipAddress }} hello"
# Tasks
- name: Try to replace the line
replace:
dest : /dir/file
replace : '{{ lineModified }} '
regexp : '{{ lineSearched }}$'
backup : yes
register : checkIfLineIsHere
# If the line not is here, I add it
- name: Add line
lineinfile:
state : present
dest : /dir/file
line : '{{ lineSearched }}'
regexp : ''
insertafter: EOF
when: checkIfLineIsHere.changed == false
# If the line is here, I still want this line in the file, Then restore it
- name: Restore the searched line.
lineinfile:
state : present
dest : /dir/file
line : '{{ lineSearched }}'
regexp : '{{ lineModified }}$'
when: checkIfLineIsHere.changed
Ok, here is mine naive solution... probably not a cross-platform and native Ansible (I've just started to use this tool and still learn it), but definitely shorter:
- name: Update /path/to/some/file
shell: grep -q 'regex' /path/to/some/file && echo exists || echo 'text-to-append' >> /path/to/some/file
register: result
changed_when: result.stdout.find('exists') == -1
We have tried the below and it worked well. our scenario need to entry "compress" in syslog file if it does not exist.
- name: Checking compress entry present if not add entry
lineinfile:
path: /etc/logrotate.d/syslog
regexp: " compress"
state: present
insertafter: " missingok"
line: " compress"
- name: add couchbase.host to properties, works like add or replace
lineinfile:
path: /database.properties
regexp: '^couchbase.host=(?!(127.0.0.1))\b'
line: 'couchbase.host=127.0.0.1'
This code will replace any line ^couchbase.host=* except couchbase.host=127.0.0.1 or will add new line if it does not exist
- name: add couchbase.host to properties, works like add or replace
lineinfile:
state: present
dest: /database.properties
regexp: '^couchbase.host'
line: 'couchbase.host=127.0.0.1'

Resources