I want to execute my Ansible task only on a specific SUSE version. But this task gets executed for all Linux OS flavors. Which is not expected. Can someone help me with this? Why is the when condition not working? What I am doing wrong?
Code:
# Action 146: addlink ld-lsb.so.3->ld-2.11.1.so in /lib on sles11.x,12.x for lmutil in cct2000739233
- name: Addlink ld-lsb.so.3->ld-2.11.1.so in /lib on sles11.x,12.x for lmutil
shell: "ls ld-*.so|grep -v lsb|head -n 1"
args:
chdir: /lib
register: ldso
- stat:
path: /lib64/ld-lsb-x86-64.so.3
register: lib64_result
- stat:
path: /lib/ld-lsb.so.3
register: lib_result
- block:
- file:
src: "/lib/{{ ldso.stdout }}"
dest: /lib/ld-lsb.so.3
state: link
force: true
- file:
src: /lib64/ld-linux-x86-64.so.2
dest: /lib64/ld-lsb-x86-64.so.3
state: link
force: true
when:
# Check the OS level. Make sure it runs only on SLES-11 SP4,SLES-12SP0/1/2/3
- ansible_distribution == 'Suse'
- ansible_distribution_major_version == "11"
- ansible_distribution_release == "4"
- ansible_distribution_version == "11.4"
- ansible_distribution == 'SLES'
- ansible_distribution_major_version == "12"
- ansible_distribution_release == "0" or "1" or "2" or "3"
- ansible_distribution_version == "12.0" or "12.1" or "12.2" or "12.3"
- not lib64_result.stat.exists|bool or not lib_result.stat.exists|bool
- ldso.stdout != ''
- debug:
msg: "{{ ldso.stdout }}"
Cannot do or like that. Has to be like this:
- ansible_distribution_release == "0" or ansible_distribution_release == "1" or ansible_distribution_release == "2" or ansible_distribution_release == "3"
- ansible_distribution_version == "12.0" or ansible_distribution_version == "12.1" or ansible_distribution_version == "12.2" or ansible_distribution_version == "12.3"
The way you have it, ansible_distribution_release == "0" or "1", ansible_distribution_release == "0" evaluates to false, that's fine, but then "1" evaluates to true. false or true then evaluates to true.
Related
I am working with Ansible and familiarizing myself with task control in a playbook. I am struggling with the fail module and fail_when statement. Here is a lab I worked on, which seems to work, but I would like to see how this could be handled using the fail module or fail_when, IF it is needed.
Here is the task I struggled with:
Install packages only if the current operating system is CentOS or RHEL version 8 or later. If that is not the case, the playbook should fail with the error message "Host hostname does not meet the minimal requirements", where hostname is replaced with the current host name.
Here are my issues:
Using ansible_facts in the fail module does not workout well it seems
I do not understand how I would use fail_when on this task
Here is my solution:
---
- name: Install packages
hosts: all
vars_files:
vars/pack.yml
tasks:
- name: Install packages
block:
- name: Install packages and loop
yum:
name: "{{ item.package }}"
state: "{{ item.state }}"
loop: "{{ packages }}"
when:
( ansible_facts['distribution'] == "CentOS" and ansible_facts['distribution_version'] == "8" )
or
( ansible_facts['distribution'] == "RedHat" and ansible_facts['distribution_version'] >= "8.1" )
- name: Copy file to /tmp
copy:
content: "Welcome to my webserver"
dest: /tmp/index.html
notify: restart web
- name: Check for firewalld
yum:
name: firewalld
state: latest
- name: verify firewalld is started
service:
name: firewalld
state: started
- name: open firewall ports for http and https
firewalld:
service: "{{ item.service }}"
state: "{{ item.state }}"
immediate: yes
permanent: yes
loop: "{{ firewall }}"
rescue:
- name: fail if any task fail
fail:
msg: did not meet the requirements
handlers:
- name: restart web
service:
name: httpd
state: restarted
I am using the RHCE exam book by Sander Van Vugt btw. This is lab 7-1. His Github is a bit lacking on the labs.
Here is the better optimized playbook:
---
- name: End of chapter lab 7 final
hosts: all
become: true
vars_files:
- vars/pack.yml
tasks:
- name: Install httpd and mod_ssl packages
yum:
name: "{{ item.package }}"
state: "{{ item.state }}"
loop: "{{ packages }}"
when:
( ansible_facts['distribution'] == "CentOS" and ansible_facts['distribution_version'] <= "8" )
or
( ansible_facts['distribution'] == "RedHat" and ansible_facts['distribution_version'] <= "8" )
- name: Fail if the following is not met
fail:
msg: "Host {{ ansible_facts['hostname'] }} does not meet the minimal requirements"
when:
not (( ansible_facts['distribution'] == "CentOS" and ansible_facts['distribution_version'] <= "8" )
or
( ansible_facts['distribution'] == "RedHat" and ansible_facts['distribution_version'] <= "8" ))
- name: Copy tmp file
copy:
content: "Welcome to my webserver"
dest: /tmp/index.html
- name: Configure Firewalld for http and https rules
firewalld:
service: "{{ item.service }}"
state: "{{ item.state }}"
immediate: yes
permanent: yes
loop: "{{ firewall }}"
Use the fail module to stop the script with a custom error message. In your case you could use it like this:
- name: fail if wrong OS
fail:
msg: "Wrong OS: {{ansible_facts['distribution']}}"
when: not (( ansible_facts['distribution'] == "CentOS" and ansible_facts['distribution_version'] == "8" )
or
( ansible_facts['distribution'] == "RedHat" and ansible_facts['distribution_version'] >= "8.1" ))
Whereas the failed_when can be used to define when some behaviour is to be considered a failure. No custom error message possible. Use it like this:
- name: check if file exists and fail if it does
stat:
path: /etc/hosts
register: result
failed_when: result.stat.exists
In your above script you used when. This one executes the task if the when expression is true, or it skips the task if the when expression is false. But it would not stop the script execution.
Could you please help me with identifying the error
'dict object' has no attribute 'stdout'
which I am getting while running an Ansible playbook. Please see the error below:
{"msg": "The conditional check '(result.stdout == 1) and (ansible_distribution == 'CentOS' or ansible_distribution == 'RedHat')' failed. The error was: error while evaluating conditional ((result.stdout == 1) and (ansible_distribution == 'CentOS' or ansible_distribution == 'RedHat')): 'dict object' has no attribute 'stdout'\n\nThe error appears to be in '/etc/ansible/playbooks/dev_patching_security_upgrade_version2.yml': line 47, column 7, but may\nbe elsewhere in the file depending on the exact syntax problem.\n\nThe offending line appears to be:\n\n\n - name: Reboot CentOS Server if Necessary\n ^ here\n"}
I am pasting the relevant part of the code below:
- name: check to see if we need a reboot in Centos
shell:
"needs-restarting; echo $?"
register: result
ignore_errors: yes
when: ansible_distribution == 'CentOS' or ansible_distribution == 'RedHat'
- name: display result
debug:
var: result.stdout
when: ansible_distribution == 'CentOS' or ansible_distribution == 'RedHat'
- name: Reboot CentOS Server if Necessary
command: shutdown -r now "Ansible Updates Triggered reboot"
become: true
async: 30
poll: 0
when: (result.stdout == 1) and (ansible_distribution == 'CentOS' or ansible_distribution == 'RedHat')
Thanks in advance
This is because the result set might not have a dictionary key stdout. To debug a result set you may use
- name: Show result
debug:
var: result
Since the shell module already provides the return code within the Return Values, reboot checks are simple as
- name: Check if reboot_required
shell:
cmd: "needs-restarting -r"
changed_when: false
failed_when: reboot_required.rc != 0 and reboot_required.rc != 1
check_mode: false
register: reboot_required
- name: Report reboot_required
debug:
msg: "{{ reboot_required.rc | bool }} "
changed_when: reboot_required.rc == 1
check_mode: false
and resulting into an output of in example
TASK [Report reboot_required] **************************************************
ok: [test1.example.com] => {
"msg": "True "
}
ok: [test2.example.com] => {
"msg": "False "
}
You could dense down the tasks to one by using one
needs-restarting -r || /usr/sbin/shutdown -r now "Ansible Updates Triggered reboot"
Further Q&A
How to determine if system needs a reboot?
Ansible - How to reboot the server based on condition?
In Ansible I have two separate tasks to get the list of existing services for redhat 6 and 7, they register to a variable, then I have another task that stops those services, if I use the same variable it gets overwritten by the last task, so it does not stop anything.
Is there a way of distinguishing between the two results? and yet only use one task to stop the services? I tried dynamic var names or creating a dictionary, but none work.
thanks
- name: Get registered services
command: bash -c "chkconfig --list | awk '{print $1}'"
register: loaded_services_{{ansible_distribution_major_version}}
when: (( ansible_os_family == "RedHat" ) and ( ansible_distribution_major_version == "6" ))
changed_when: False
tags: test
- name: Get registered services
command: bash -c "systemctl list-unit-files | grep enabled | cut -d. -f1"
register: loaded_services_{{ansible_distribution_major_version}}
when: (( ansible_os_family == "RedHat" ) and ( ansible_distribution_major_version == "7" ))
changed_when: False
tags: test
- name: shutdown unnecessary services
service: name={{ item }} enabled=no state=stopped
with_items: "{{ disable_services | intersect(loaded_services_{{ansible_distribution_major_version}}.stdout_lines)}}"
when: ansible_os_family == "RedHat" and ansible_distribution_major_version == "6"
tags:
- harden
- test
ignore_errors: yes
Try with blocks, first in this way:
- block:
- name: Get registered services
command: bash -c "chkconfig --list | awk '{print $1}'"
register: loaded_services
changed_when: False
tags: test
when: (( ansible_os_family == "RedHat" ) and ( ansible_distribution_major_version == "6" ))
- block:
- name: Get registered services
command: bash -c "systemctl list-unit-files | grep enabled | cut -d. -f1"
register: loaded_services
changed_when: False
tags: test
when: (( ansible_os_family == "RedHat" ) and ( ansible_distribution_major_version == "7" ))
- name: shutdown unnecessary services
service: name={{ item }} enabled=no state=stopped
with_items: "{{ disable_services | intersect(loaded_services.stdout_lines)}}"
when: ansible_os_family == "RedHat" and ansible_distribution_major_version == "6"
tags:
- harden
- test
ignore_errors: yes
And if doesn´t work, try in this other way:
- block:
- name: Get registered services
command: bash -c "chkconfig --list | awk '{print $1}'"
register: loaded_services
changed_when: False
tags: test
- name: shutdown unnecessary services
service: name={{ item }} enabled=no state=stopped
with_items: "{{ disable_services | intersect(loaded_services.stdout_lines)}}"
tags:
- harden
- test
ignore_errors: yes
when: (( ansible_os_family == "RedHat" ) and ( ansible_distribution_major_version == "6" ))
- block:
- name: Get registered services
command: bash -c "systemctl list-unit-files | grep enabled | cut -d. -f1"
register: loaded_services
changed_when: False
tags: test
- name: shutdown unnecessary services
service: name={{ item }} enabled=no state=stopped
with_items: "{{ disable_services | intersect(loaded_services.stdout_lines)}}"
tags:
- harden
- test
ignore_errors: yes
when: (( ansible_os_family == "RedHat" ) and ( ansible_distribution_major_version == "7" ))
I have some playbook for ubuntu and centos and I want to use main.yml to check when: ansible_os_family == 'RedHat' or ansible_distribution == 'Centos', run playbooks ( as some ans many :-) ).
When I run just:
-include: centos-xxx.yml
-include: centos-xaa.yml
-include: centos-xsss.yml
It will run all of them
Basically I want that the playbook will run if meet condition.
I didn't find any doc that say how to run include: more then one i am trying to not make task per include if possible.
You can use the when conditional to include files. This is somewhat common, in fact.
- include: centos-xxx.yml
when: ansible_os_family == 'RedHat' or ansible_distribution == 'Centos'
- include: debian-xxx.yml
when: ansible_distribution == 'Debian'
Per your comment- if you want to run them in order, you have two options. Here's the straightforward:
- include: centos-a.yml
when: ansible_os_family == 'RedHat' or ansible_distribution == 'Centos'
- include: centos-b.yml
when: ansible_os_family == 'RedHat' or ansible_distribution == 'Centos'
- include: centos-c.yml
when: ansible_os_family == 'RedHat' or ansible_distribution == 'Centos'
- include: centos-d.yml
when: ansible_os_family == 'RedHat' or ansible_distribution == 'Centos'
Or, you can do this:
- include: centos.yml
when: ansible_os_family == 'RedHat' or ansible_distribution == 'Centos'
and inside centos.yml:
- include: centos-a.yml
- include: centos-b.yml
- include: centos-c.yml
- include: centos-d.yml
I am working on a ansible playbook for my servers (mix of ubuntu and centos) and when trying to transfer some config files for monit if a program is installed I am running into an issue. It works perfectly on my centos machines but the ubuntu one's it transfers the template no matter what completely ignoring the conditional.
---
- name: Check for Sendmail (Ubuntu)
shell: dpkg-query -W -f='${Status} ${Version}\n' sendmail
register: ubuntu_installed
when: ansible_distribution == 'Debian' or ansible_distribution == 'Ubuntu'
changed_when: False
failed_when: "'FAILED' in ubuntu_installed.stderr"
- debug: var=ubuntu_installed
- name: Check for Sendmail (CentOs)
shell: rpm -qa | grep sendmail
register: cent_installed
when: ansible_distribution == 'CentOS' or ansible_distribution == 'Red Hat Enterprise Linux'
changed_when: False
failed_when: "'FAILED' in cent_installed.stderr"
- name: Transfer Monit config files for Sendmail (Ubuntu)
template: src=monit/templates/sendmail.j2 dest=/etc/monit/conf.d/sendmail owner=root group=root mode=644
when: ubuntu_installed.skipped is not defined and ubuntu_installed.stdout != "" and ansible_distribution == 'Debian' or ansible_distribution == 'Ubuntu'
- name: Transfer Monit config files for Sendmail (CentOs)
template: src=monit/templates/sendmail.j2 dest=/etc/monit.d/sendmail owner=root group=root mode=644
when: cent_installed.skipped is not defined and cent_installed.stdout != "" and ansible_distribution == 'CentOS' or ansible_distribution == 'Red Hat Enterprise Linux'
I have the debug in there to check to make sure the variable is right. When I run the playbook I get...
TASK: [monit | debug var=ubuntu_installed] ************************************
ok: [server1] => {
"item": "",
"ubuntu_installed": {
"changed": false,
"cmd": "dpkg-query -W -f='${Status} ${Version}\\n' sendmail ",
"delta": "0:00:00.012985",
"end": "2014-07-11 16:56:12.688509",
"failed": false,
"failed_when_result": false,
"invocation": {
"module_args": "dpkg-query -W -f='${Status} ${Version}\\n' sendmail",
"module_name": "shell"
},
"item": "",
"rc": 1,
"start": "2014-07-11 16:56:12.675524",
"stderr": "dpkg-query: no packages found matching sendmail",
"stdout": "",
"stdout_lines": []
}
}
It is completely ignoring the conditional ubuntu_installed.stdout != ""
Here's your conditional:
- when: ubuntu_installed.skipped is not defined and ubuntu_installed.stdout != "" and ansible_distribution == 'Debian' or ansible_distribution == 'Ubuntu'
Your precedence is incorrect: A and B and C or D means is (A and B and C) or D, which is not what you want. (note Ansible conditionals use Jinja2 expressions, which reflect Python conditionals, so here are the precedence rules).
Lazy programmers add parenthesis for clarity; here's the revised conditional:
- when: (ubuntu_installed.skipped is not defined and ubuntu_installed.stdout != "") and (ansible_distribution == 'Debian' or ansible_distribution == 'Ubuntu')
PS- you showed the output to one command, debug, but it's helpful to give alllll the output. That's ansible-playbook -vvv.