Only check whether a line present in a file (ansible)
AnsibleAnsible Problem Overview
In ansible, I need to check whether a particular line present in a file or not. Basically, I need to convert the following command to an ansible task. My goal is to only check.
grep -Fxq "127.0.0.1" /tmp/my.conf
Ansible Solutions
Solution 1 - Ansible
Use check_mode, register and failed_when in concert. This fails the task if the lineinfile module would make any changes to the file being checked. Check_mode ensures nothing will change even if it otherwise would.
- name: "Ensure /tmp/my.conf contains '127.0.0.1'"
lineinfile:
name: /tmp/my.conf
line: "127.0.0.1"
state: present
check_mode: yes
register: conf
failed_when: (conf is changed) or (conf is failed)
Solution 2 - Ansible
- name: Check whether /tmp/my.conf contains "127.0.0.1"
command: grep -Fxq "127.0.0.1" /tmp/my.conf
register: checkmyconf
check_mode: no
ignore_errors: yes
changed_when: no
- name: Greet the world if /tmp/my.conf contains "127.0.0.1"
debug: msg="Hello, world!"
when: checkmyconf.rc == 0
Update 2017-08-28: Older Ansible versions need to use always_run: yes
instead of check_mode: no
.
Solution 3 - Ansible
User robo's regexp
& absent
method is quite clean, so I've fleshed it out here for easy use and added improvements from comments by @assylias and @Olivier:
- name: Ensure /tmp/my.conf contains 127.0.0.1
lineinfile:
path: /tmp/my.conf
regexp: '^127\.0\.0\.1.*whatever'
state: absent
check_mode: yes
changed_when: false
register: out
- debug:
msg: "Yes, line exists."
when: out.found
- debug:
msg: "Line does NOT exist."
when: not out.found
Solution 4 - Ansible
With the accepted solution, even though you ignore errors, you will still get ugly red error output on the first task if there is no match:
TASK: [Check whether /tmp/my.conf contains "127.0.0.1"] ***********************
failed: [localhost] => {"changed": false, "cmd": "grep -Fxq "127.0.0.1" /tmp/my.conf", "delta": "0:00:00.018709", "end": "2015-09-27 17:46:18.252024", "rc": 1, "start": "2015-09-27 17:46:18.233315", "stdout_lines": [], "warnings": []}
...ignoring
If you want less verbose output, you can use awk
instead of grep
. awk
won't return an error on a non-match, which means the first check task below won't error regardless of a match or non-match:
- name: Check whether /tmp/my.conf contains "127.0.0.1"
command: awk /^127.0.0.1$/ /tmp/my.conf
register: checkmyconf
changed_when: False
- name: Greet the world if /tmp/my.conf contains "127.0.0.1"
debug: msg="Hello, world!"
when: checkmyconf.stdout | match("127.0.0.1")
Notice that my second task uses the match filter as awk returns the matched string if it finds a match.
The alternative above will produce the following output regardless of whether the check task has a match or not:
TASK: [Check whether /tmp/my.conf contains "127.0.0.1"] ***********************
ok: [localhost]
IMHO this is a better approach as you won't ignore other errors in your first task (e.g. if the specified file did not exist).
Solution 5 - Ansible
Use ansible lineinfile command, but this command will update the file with the line if it does not exists.
- lineinfile: dest=/tmp/my.conf line='127.0.0.1' state=present
Solution 6 - Ansible
Another way is to use the "replace module" then "lineinfile module".
The algo is closed to the one used when you want to change the values of two variables.
- First, use "replace module" to detect if the line you are looking for is here and change it with the something else. (Like same line + something at the end).
- Then if "replace" is true, It means your line is here then replace the new line with a particularity, with the new line looking.
- Else the line you are looking for is not here.
Example:
# Vars
- name: Set parameters
set_fact:
newline : "hello, i love ansible"
lineSearched : "hello"
lineModified : "hello you"
# Tasks
- name: Try to replace the line
replace:
dest : /dir/file
replace : '{{ lineModified }} '
regexp : '{{ lineSearched }}$'
backup : yes
register : checkIfLineIsHere
- name: Line is here, change it
lineinfile:
state : present
dest : /dir/file
line : '{{ newline }}'
regexp : '{{ lineModified }}$'
when: checkIfLineIsHere.changed
- If the file contains "hello", it will become "hello you" then "hello, i love ansible" at the end.
- If the file content doesn't contain "hello", the file is not modified.
With the same idea, you can do something if the lineSearched is here:
# Vars
- name: Set parameters
set_fact:
newline : "hello, i love ansible"
lineSearched : "hello"
lineModified : "hello you"
# Tasks
- name: Try to replace the line
replace:
dest : /dir/file
replace : '{{ lineModified }} '
regexp : '{{ lineSearched }}$'
backup : yes
register : checkIfLineIsHere
# If the line is here, I want to add something.
- name: If line is here, do something
lineinfile:
state : present
dest : /dir/file
line : '{{ newline }}'
regexp : ''
insertafter: EOF
when: checkIfLineIsHere.changed
# But 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
- If the file contains "hello", the line will still contain "hello" and "hello, i love ansible" at the end.
- If the file content doesn't contain "hello", the file is not modified.
Solution 7 - Ansible
You can use the file plugin for this scenario.
To set a fact you can use in other tasks ... this works.
- name: Check whether /tmp/my.conf contains "127.0.0.1"
set_fact:
myconf: "{{ lookup('file', '/tmp/my.conf') }}"
ignore_errors: yes
- name: Greet the world if /tmp/my.conf contains "127.0.0.1"
debug: msg="Hello, world!"
when: "'127.0.0.1' in myconf"
To check the file content as a condition of a task ... this should work.
- name: Greet the world if /tmp/my.conf contains "127.0.0.1"
debug: msg="Hello, world!"
when: "'127.0.0.1' in lookup('file', '/tmp/my.conf')"
Solution 8 - Ansible
Another solution, also useful for other purposes is to loop over the contents of the file, line by line
- name: get the file
slurp:
src: /etc/locale.gen
register: slurped_file
- name: initialize the matches list
set_fact:
MATCHES: []
- name: collect matches in a list
set_fact:
MATCHES: "{{ MATCHES + [line2match] }}"
loop: "{{ file_lines }}"
loop_control:
loop_var: line2match
vars:
- decode_content: "{{ slurped_file.content | b64decode }}"
- file_lines: "{{ decode_content.split('\n') }}"
when: '"BE" in line2match'
- name: report matches if any
debug:
msg: "Found {{ MATCHES | length }} matches\n{{ MATCHES }}"
when: 'listlen | int > 0'
vars:
listlen: "{{ MATCHES | length }}"
This mechanism can be use to get a specific value from a matched line if you use it for example with the jinja regex_replace filter