Ansible loops in lineinfile - vagrant

Commenting out multiple lines should work with a standard loop [1] like this:
- name: "Allow /srv folder accessed by default. Just comment out the lines to allow."
lineinfile: dest=/etc/apache2/apache2.conf regexp={{ item.regexp }} line={{ item.line }} state=present
with_items:
- { regexp: '#<Directory /srv/>', line: '<Directory /srv/>' }
But I got an error:
failed: [192.168.101.101] => (item={'regexp': '#<Directory /srv/>', 'line': '<Directory /srv/>'}) => {"failed": true, "item": {"line": "<Directory /srv/>", "regexp": "#<Directory /srv/>"}}
msg: this module requires key=value arguments (['dest=/etc/apache2/apache2.conf', 'regexp=#<Directory', '/srv/>', 'line=<Directory', '/srv/>', 'state=present'])
FATAL: all hosts have already failed -- aborting
So how to get this working with multiple lines/items?
[1] http://docs.ansible.com/playbooks_loops.html#standard-loops

Thank you, tedder42! You we're more than right.
To be idempotent, the lineinfile task needs to match both the commented and uncommented state of the line so we start it: ^#?
So the fully functioning play set out to be:
- name: "Allow /srv folder accessed by default. Comment out the lines to allow. "
lineinfile:
dest=/etc/apache2/apache2.conf
regexp="{{ item.regexp }}"
line="{{ item.line }}"
state=present
with_items:
- { regexp: '^#?<Directory /srv/>', line: '<Directory /srv/>' }
- { regexp: '^#?\tOptions Indexes FollowSymLinks', line: '\tOptions Indexes FollowSymLinks' }
- { regexp: '^#?\tAllowOverride None', line: '\tAllowOverride None' }
- { regexp: '^#?\tRequire all granted', line: '\tRequire all granted' }
- { regexp: '^#?</Directory>', line: '</Directory>'}
This is actually not a good idea. Definitely better is use the copy with backup=yes.

You were really close to having it working. Simply add quotes around the regexp and line.
lineinfile: dest=/etc/apache2/apache2.conf regexp="{{ item.regexp }}" line="{{ item.line }}" state=present
I wasn't entirely sure, but the error message implied there were problems with seeing the regexp and line args, so I tried a few things.
As a reminder, lineinfile is somewhat of an antipattern. When you find yourself using it, that's a sign you should consider switching to copy or template.

Related

Ansible: Modify cmdline.txt on Raspberry Pi

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

ansible playbook find/replace acting weird on second run

I have this task that finds 2 lines in sshd_config, and changes them. This works perfectly on the first run, it replaces the src lines with the dest lines. But if I run it a second time, after the 2 lines in the file are already correct, it appends 2 new lines to the bottom of the file.
I tried adding state: present but it didn't change the behavior.
- name: Configuring sshd_config
lineinfile:
path: /etc/ssh/sshd_config
regexp: "{{ item.src }}"
state: present
line: "{{ item.dest }}"
with_items:
- { src: "#PrintLastLog yes", dest: "PrintLastLog no" }
- { src: "#Banner none", dest: "Banner /etc/issue.net" }
I was expecting it to not match the src lines and make no changes.
What you are getting is the exact expected behaviour. Quoting the documentation about the regexp parameter when using state: present
When modifying a line the regexp should typically match both the initial state of the line as well as its state after replacement by line to ensure idempotence.
Modifying your task as follow should do the job.
- name: Configuring sshd_config
lineinfile:
path: /etc/ssh/sshd_config
regexp: "{{ item.src }}"
state: present
line: "{{ item.dest }}"
with_items:
- { src: "^(# *)?PrintLastLog", dest: "PrintLastLog no" }
- { src: "^(# *)?Banner", dest: "Banner /etc/issue.net" }
This will match the commented line (with optionnal spaces after the hash) or the newly modified line.

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 playbook, difficults with with_items multiples vars

I use ansible to configure a server. I have to see if some line exist and if I have to change them so I use lineinfile module. To have a code more clean I would use with_items like this:
- name: Postegresql configuration
lineinfile: dest={{ item.dest }} line={{ item.line }} regexp={{ item.regexp }}
with_items:
- { dest: '/var/lib/pgsql/data/postgresql.conf', line: 'port = 5433', regexp: '^port =' }
- { dest: '/var/lib/pgsql/data/postgresql.conf', line: 'log_truncate_on_rotation = on', regexp: '^log_truncate_on_rotation =' }
- { dest: '/var/lib/pgsql/data/postgresql.conf', line: 'log_rotation_age = 1d', regexp: '^log_rotation_age =' }
- { dest: '/var/lib/pgsql/data/postgresql.conf', line: 'log_rotation_size = 0MB', regexp: '^log_rotation_size =' }
notify: restart postgresql
tags: verif
But it doesn't work. I he=ave this error:
a duplicate parameter was found in the argument string ()
I this it's a syntax error, can you help me ?
Try putting quotes around any inserted variables that you use. This will ensure that they are treated as a whole together and get parsed as you intend.
lineinfile: dest="{{ item.dest }}" line="{{ item.line }}" regexp="{{ item.regexp }}"

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