Ansible - variable changing it's value even though the condition is not met - conditional-statements

I have a role that I need to use for more values. For each task within the role I register a variable: checkdeps (it's the same for all tasks within this role - during a run it always has at least one value/output - I need it like this because the path differs "/opt/play/apps/default-ace", "default-device" etc.) and in the end I do an echo to view the output of checkdeps.stdout.
Below I've put one task that will output ok and one that will intentionally will be skipped.
If I use the parameter dep: APK_PARSER in the playbook what it does is: first checkdeps registers the output and in the second task the value of checkdeps is replaced with nothing! Even though the task is skipped due to no matching dep parameter.
Why does the value of checkdeps is replaced if the condition is not met ?
- name: "output ok"
shell: "cd /opt/play/apps/default-ace && play deps {{ dep }}"
register: checkdeps
when: "dep == \"APK_PARSER\""
- name: "example to skip"
shell: "cd /opt/play/apps/default-device && play deps {{ dep }}"
register: checkdeps
when: "dep == \"I\" or dep == \"II\""
- name: "echo ok if Done!"
shell: "echo \"OK - {{ dep }} Dependencies {{ checkdeps.stdout }}\""
And it gives me error:
One or more undefined variables: 'dict' object has no attribute 'stdout'
I've modified the last line without the stdout:
shell: "echo \"OK - {{ dep }} Dependencies {{ checkdeps }}\""
and it ran without error but gave the wrong output:
stdout:
OK - APK_PARSER Dependencies {u'skipped': True, u'changed': False}
did the variable checkdeps register the "skipping: [...]" ? Why it is changing it's value if the condition is not met ?

ansible stores the "log of ansible task execution", NOT the 'output of command executed'. This log which is a dict and one of the keys is stdout, which contains everything the executed command printed on stdout (output of command).
tasks:
- debug: msg='one'
register: o1
when: True
- debug: msg='two'
register: o2
when: False
- debug: msg='o1={{o1}}'
- debug: msg='o2={{o2}}'
It prints the following. 'skipped' & 'changed' are the two keys that would be present in the "log" when the task is not executed.
TASK: [debug msg='one'] *******************************************************
ok: [localhost] => {
"msg": "one"
}
TASK: [debug msg='two'] *******************************************************
skipping: [localhost]
TASK: [debug msg='o1={{o1}}'] *************************************************
ok: [localhost] => {
"msg": "o1={'msg': u'one', 'verbose_always': True, 'invocation': {'module_name': u'debug', 'module_args': u\"msg='one'\"}}"
}
TASK: [debug msg='o2={{o2}}'] *************************************************
ok: [localhost] => {
"msg": "o2={u'skipped': True, u'changed': False}"
}
* term "task execution log" is invented by me for explanation and not ansible standard terminology.

Or simply tell ansible to not register the variable if the task is skipped with set_when_task_skipped=false:
- name: "example to skip"
shell: "cd /opt/play/apps/default-device && play deps {{ dep }}"
register: checkdeps set_when_task_skipped=false
when: "dep == \"I\" or dep == \"II\""

Related

How to deal with multiple when condition for registered variable in ansible

I have a playbook 3 raw task (or more) with sample commands like below:
Playbook mytest.yml
- hosts: remotehost
gather_facts: no
tasks:
- name: Execute command1
raw: "ls -ltr"
register: cmdoutput
when: remcmd == "list"
- name: Execute command2
raw: "hostname"
register: cmdoutput
when: remcmd == "host"
- name: Execute command3
raw: "uptime"
register: cmdoutput
when: remcmd == "up"
- hosts: localhost
gather_facts: no
tasks:
- debug:
msg: "Printing {{ hostvars['remotehost']['cmdoutput'] }}"
This is my nventory myhost.yml
[remotehost]
myserver1
Here is how I run the playbook:
ansible-playbook -i myhost.yml mytest.yml -e remcmd="host"
PLAY [remotehost] ***************************************************************************************************************
TASK [Execute command1] *********************************************************************************************************
Thursday 06 October 2022 07:06:06 -0500 (0:00:00.013) 0:00:00.013 ******
skipping: [myserver1]
TASK [Execute command2] *********************************************************************************************************
Thursday 06 October 2022 07:06:06 -0500 (0:00:00.023) 0:00:00.036 ******
changed: [myserver1]
TASK [Execute command3] *********************************************************************************************************
Thursday 06 October 2022 07:06:06 -0500 (0:00:00.521) 0:00:00.557 ******
skipping: [myserver1]
PLAY [localhost] ****************************************************************************************************************
TASK [debug] ********************************************************************************************************************
Thursday 06 October 2022 07:06:06 -0500 (0:00:00.032) 0:00:00.590 ******
fatal: [localhost]: FAILED! => {"msg": "The task includes an option with an undefined variable. The error was: \"hostvars['remotehost']\" is undefined\n\nThe error appears to be in '/home/wladmin/mytest.yml': line 22, column 7, but may\nbe elsewhere in the file depending on the exact syntax problem.\n\nThe offending line appears to be:\n\n tasks:\n - debug:\n ^ here\n"}
PLAY RECAP **********************************************************************************************************************
localhost : ok=0 changed=0 unreachable=0 failed=1 skipped=0 rescued=0 ignored=0
myserver1 : ok=1 changed=1 unreachable=0 failed=0 skipped=2 rescued=0 ignored=0
My requirement is no matter what value is passed for remcmd my localhost play should print stdoutlines of cmdoutput
Preliminary notes:
Using raw is evil.
Don't use raw unless to install prereqs (i.e. python) on the target host. Then switch to modules or at the very least command/shell
If you still intend to use raw, go back to point 1 above
In case your forgot to go back to point 1: using raw is evil
Don't register several tasks with the same var name (the last one always win, even if skipped). Don't create tasks you can avoid up-start.
As an illustration of the above principles
- hosts: remotehost
gather_facts: no
vars:
cmd_map:
list: ls -ltr
host: hostname
up: uptime
tasks:
- name: Make sure remcmd is known
assert:
that: remcmd in cmp_map.keys()
fail_msg: "remcmd must be one of: {{ cmd_map.keys() | join(', ') }}"
- name: Execute command
command: "{{ cmd_map[remcmd] }}"
register: cmdoutput
- name: Show entire result from above task
debug:
var: cmdoutput
my localhost play should print stdout_lines of cmdoutput
As far as I understand "How the debug module works", it can only print on the Control Node.
Therefore you could just remove three (3) lines in your example
- hosts: localhost
gather_facts: no
tasks:
and give it a try with
- hosts: remotehost
gather_facts: no
tasks:
- name: Execute command1
raw: "ls -ltr"
register: cmdoutput
when: remcmd == "list"
- name: Execute command2
raw: "hostname"
register: cmdoutput
when: remcmd == "host"
- name: Execute command3
raw: "uptime"
register: cmdoutput
when: remcmd == "up"
- debug:
msg: "Printing {{ cmdoutput }}"
and independently of which task became executed the result would be provided.
Apart from the answer about "How the debug module works" here, I like to recommended to proceed further with the answer of Zeitounator, since it will address your possible use case more complete.

How to alert via email in Ansible

I have setup a mail task in ansible to send emails if yum update is marked as 'changed'.
Here is my current working code:
- name: Send mail alert if updated
community.general.mail:
to:
- 'recipient1'
cc:
- 'recipient2'
subject: Update Alert
body: 'Ansible Tower Updates have been applied on the following system: {{ ansible_hostname }}'
sender: "ansible.updates#domain.com"
delegate_to: localhost
when: yum_update.changed
This works great, however, every system that gets updated per host group sends a separate email. Last night for instance I had a group of 20 servers update and received 20 separate emails. I'm aware of why this happens, but my question is how would I script this to add all the systems to one email? Is that even possible or should I just alert that the group was updated and inform teams of what servers are in each group? (I'd prefer not to take the second option)
Edit 1:
I have added the code suggested and am now unable to receive any emails. Here's the error message:
"msg": "The conditional check '_changed|length > 0' failed. The error was: error while evaluating conditional (_changed|length > 0): {{ hostvars|dict2items| selectattr('value.yum_update.changed')| map(attribute='key')|list }}: 'ansible.vars.hostvars.HostVarsVars object' has no attribute 'yum_update'\n\nThe error appears to be in '/tmp/bwrap_1073_o8ibkgrl/awx_1073_0eojw5px/project/yum-update-ent_template_servers.yml': line 22, 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: Send mail alert if updated\n ^ here\n",
I am also attaching my entire playbook for reference:
---
- name: Update enterprise template servers
hosts: ent_template_servers
tasks:
- name: Update all packages
yum:
name: '*'
state: latest
register: yum_update
- name: Reboot if needed
import_tasks: /usr/share/ansible/tasks/reboot-if-needed-centos.yml
- name: Kernel Cleanup
import_tasks: /usr/share/ansible/tasks/kernel-cleanup.yml
- debug:
var: yum_update.changed
- name: Send mail alert if updated
community.general.mail:
to:
- 'email#domain.com'
subject: Update Alert
body: |-
Updates have been applied on the following system(s):
{{ _changed }}
sender: "ansible.updates#domain.com"
delegate_to: localhost
run_once: true
when: _changed|length > 0
vars:
_changed: "{{ hostvars|dict2items|
selectattr('yum_update.changed')|
map(attribute='key')|list }}"
...
Ansible version is: 2.9.27
Ansible Tower version is: 3.8.3
Thanks in advance!
For example, the mail task below
- debug:
var: yum_update.changed
- community.general.mail:
sender: ansible
to: root
subject: Update Alert
body: |-
Updates have been applied to the following system:
{{ _changed }}
delegate_to: localhost
run_once: true
when: _changed|length > 0
vars:
_changed: "{{ hostvars|dict2items|
selectattr('value.yum_update.changed')|
map(attribute='key')|list }}"
TASK [debug] ***************************************************************
ok: [host01] =>
yum_update.changed: true
ok: [host02] =>
yum_update.changed: false
ok: [host03] =>
yum_update.changed: true
TASK [community.general.mail] **********************************************
ok: [host01 -> localhost]
will send
From: ansible#domain.com
To: root#domain.com
Cc:
Subject: Update Alert
Date: Wed, 09 Feb 2022 16:55:47 +0100
X-Mailer: Ansible mail module
Updates have been applied to the following system:
['host01', 'host03']
Remove the condition below if you want to receive also empty lists
when: _changed|length > 0
Debug
'ansible.vars.hostvars.HostVarsVars object' has no attribute 'yum_update'
Q: "What I could try?"
A: Some of the hosts are missing the variables yum_update. You can test it
- debug:
msg: "{{ hostvars|dict2items|
selectattr('value.yum_update.changed')|
map(attribute='key')|list }}"
run_once: true
Either make sure that the variable is defined on all hosts or use json_query. This filter tolerates missing attributes, e.g.
- debug:
msg: "{{ hostvars|dict2items|
json_query('[?value.yum_update.changed].key') }}"
run_once: true
Q: "The 'debug' task prior to the 'mail' task gives me the same output. But it fails when the 'mail' task is executed."
A: Minimize the code and isolate the problem. For example, in the code below you can see
Variable yum_update.changed is missing on host03
The filter json_query ignores this
The filter selectattr fails
- debug:
var: yum_update.changed
- debug:
msg: "{{ hostvars|dict2items|
json_query('[?value.yum_update.changed].key') }}"
run_once: true
- debug:
msg: "{{ hostvars|dict2items|
selectattr('value.yum_update.changed')|
map(attribute='key')|list }}"
run_once: true
gives
TASK [debug] **************************************************
ok: [host01] =>
yum_update.changed: true
ok: [host02] =>
yum_update.changed: false
ok: [host03] =>
yum_update.changed: VARIABLE IS NOT DEFINED!
TASK [debug] **************************************************
ok: [host01] =>
msg:
- host01
TASK [debug] **************************************************
fatal: [host01]: FAILED! =>
msg: |-
The task includes an option with an undefined variable.
The error was: 'ansible.vars.hostvars.HostVarsVars object'
has no attribute 'yum_update'
Both filters give the same results if all variables are present
TASK [debug] **************************************************
ok: [host01] =>
yum_update.changed: true
ok: [host02] =>
yum_update.changed: false
ok: [host03] =>
yum_update.changed: true
TASK [debug] **************************************************
ok: [host01] =>
msg:
- host01
- host03
TASK [debug] **************************************************
ok: [host01] =>
msg:
- host01
- host03

How to detect unreachable target hosts in ansible

I wish to grab in a variable sshreachable if a target hosts all_hosts are reachable or not.
I wrote the below playbook for the same.
- name: Play 3- check telnet nodes
hosts: localhost
ignore_unreachable: yes
- name: Check all port numbers are accessible from current host
include_tasks: innertelnet.yml
with_items: "{{ groups['all_hosts'] }}"
cat innertelnet.yml
---
- name: Check ssh connectivity
block:
- raw: "ssh -o BatchMode=yes root#{{ item }} echo success"
ignore_errors: yes
register: sshcheck
- debug:
msg: "SSHCHECK variable:{{ sshcheck }}"
- set_fact:
sshreachable: 'SSH SUCCESS'
when: sshcheck.unreachable == 'false'
- set_fact:
sshreachable: 'SSH FAILED'
when: sshcheck.unreachable == 'true'
- debug:
msg: "INNERSSH1: {{ sshreachable }}"
Unfortunately, i get error like below:
Output:
TASK [raw] *********************************************************************
fatal: [localhost]: UNREACHABLE! => {"changed": false, "msg": "Failed to connect to the host via ssh: Shared connection to 10.9.9.126 closed.", "skip_reason": "Host localhost is unreachable", "unreachable": true}
TASK [debug] ***********************************************************************************************************************************************************
task path:
ok: [localhost] => {
"msg": "SSHCHECK variable:{'msg': u'Failed to connect to the host via ssh: Shared connection to 10.9.9.126 closed.', 'unreachable': True, 'changed': False}"
}
TASK [set_fact] ****************************************************************
skipping: [localhost]
TASK [set_fact] ****************************************************************
skipping: [localhost]
TASK [debug] *******************************************************************
fatal: [localhost]: FAILED! => {"msg": "The task includes an option with an undefined variable. The error was: 'sshreachable' is undefined\n\nThe error appears to be in '/app/playbook/checkssh/innertelnet.yml': line 45, column 10, but may\nbe elsewhere in the file depending on the exact syntax problem.\n\nThe offending line appears to be:\n\n\n - debug:\n ^ here\n"}
PLAY RECAP *********************************************************************
10.0.116.194 : ok=101 changed=1 unreachable=9 failed=0 skipped=12 rescued=0 ignored=95
localhost : ok=5 changed=0 unreachable=1 failed=1 skipped=4 rescued=0 ignored=0
Can you please suggest changes to my code to get this to work?
The error seems to indicate that sshreachable variable is not getting set as the when: condition does not match. I.e. sshcheck.unreachable might not be something returned by raw.
For this purpose, command module should be enough, and we can evaluate the return code of the command to set_fact.
You could do something like:
- block:
- command: ssh -o BatchMode=yes user#host1 echo success
ignore_errors: yes
register: sshcheck
- set_fact:
sshreachable: "{{ sshcheck is success }}"
- debug:
msg: "Host1 reachable: {{ sshreachable | string }}"
Update:
raw module seems to work the same way. Example (including #mdaniel's valuable input):
- block:
- raw: ssh -o BatchMode=yes user#host1 echo success
ignore_errors: yes
register: sshcheck
- set_fact:
sshreachable: SSH SUCCESS
when: sshcheck is success
- set_fact:
sshreachable: SSH FAILED
when: sshcheck is failed
- debug:
msg: "Host1 reachable: {{ sshreachable }}"

Ansible: Lookup variables dynamically in v2.3

I have a set of variables and a task as follows. My intent is to dynamically do a healthcheck based on the URL the user chose.
vars:
current_hostname: "{{ ansible_hostname }}"
hc_url1: "https://blah1.com/healthcheck"
hc_url2: "https://blah2.com/healthcheck"
tasks:
- name: Notification Msg For Healthcheck
shell: "echo 'Performing healthcheck at the URL {{ lookup('vars', component) }} on host {{ current_hostname }}'"
Run playbook in Ansible 2.3
ansible-playbook ansible_playbook.yml -i inventory -k -v --extra-vars "component=hc_url1"
Error
fatal: [hostname]: FAILED! => {"failed": true, "msg": "lookup plugin (vars) not found"}
I know this happens because lookup plugin "var" was introduced in Ansible v2.5. Is there a way to do this in Ansible 2.3? I want get the value of {{ component }}, and then the value of {{ hc_url1 }}
PS - upgrading to 2.5 is not an option because of org restrictions
Alternatively, maybe you can do this using a dictionary.
For example,
vars:
current_hostname: "{{ ansible_hostname }}"
urls:
hc_url1: "https://blah1.com/healthcheck"
hc_url2: "https://blah2.com/healthcheck"
tasks:
- name: Notification Msg For Healthcheck
shell: "echo 'Performing healthcheck at the URL {{ urls[component] }} on host {{ current_hostname }}'"
That way, the user provided value of component will just be looked up as a key in the dictionary.

How to add more than one value to a variable in ansible playbook?

I have defined a value to variable in my playbook as :
war_name: abc
and i m calling this war_name in the roles as :
- name: Download war file
get_url:
url=http://url/directory/packages/tomcat/{{ war_name }}.war
Now the problem is I have to assign 2 values to variable war_name in playbook
like
war_name: abc,xyz
How can i do this in my playbook?
If used with_items as :
- name: Download war file
get_url:
url=http://url/directory/packages/tomcat/{{ item }}.war
with_items:
- abc
- xyz
when: "'server' in app_name"
It results in an error as :
TASK [tomcat : Download war file]
********************************************** failed: [10.x.x.x] (item=abc) => {"failed": true, "item": "abc", "msg": "missing required
arguments: dest"} failed: [10.x.x.x] (item=xyz) => {"failed": true,
"item": "xyz", "msg": "missing required arguments: dest"}
You are missing the dest argument that is required for get_url
Here is the modified version of your task:
- name: Download war file
get_url:
url: "http://url/directory/packages/tomcat/{{ item }}.war"
dest: "/path-where-you-want-to-place/{{ item }}"
with_items:
- abc
- xyz
when: "'server' in app_name"
Hope that help you
what you can do is something like this
war_name:
war1: one
war2: two
This is basically a dictionary. Though i am not sure what actually you wanna do here? I guess you want to place 2 wars at a same location with different names, if that is the case you can actually use with_items