How best to setup an Ansible block and run the block if a command does not exist or have specific output?
If the command does not exist then stdout would not be available.
- name: Check if mybin exists
stat:
path: /usr/sbin/mybin
register: mybin
- name: Get mybin status
command: mybin status
register: status
when: mybin.stat.exists
changed_when: false
- name: This node is not yet configured so run this block
block:
- name: Install req packages
....
when:
- mybin.stat.exists == False
- status.stdout.find('successfully setup') == False
The conditional check 'status.stdout.find('successfully setup') == False' failed. The error was: error while evaluating conditional (status.stdout.find('successfully setup') == False): 'dict object' has no attribute 'stdout'
As the play is written, those two conditions can never both be true at the same time. You have written the conditions as and-ed together. You need or:
when: mybin.stat.exists == False or status.stdout.find('successfully setup') == False
Related
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?
Ansible: 2.9
When should you use != or is not in when before include: create_dir.yml in Ansible?
Example code:
- name: "Get stat"
stat:
path: "/tmp"
register: stat_result
- include: create_dir.yml
when: stat_result.exists is not True
First of, you do have two issues in the tasks you are producing here:
you want to use the module file or the module stat but not a strange mixture of both
the stat module would give you a complex type stat containing the key exists, so really you should test stat_result.stat.exists
So, the explanation below is based on the tasks:
- stat:
path: "/tmp"
register: stat_result
- include: create_dir.yml
when: stat_result.stat.exists is not true
Which is a working couple of tasks.
As pointed in Jinja's documentation:
is: Performs a test.
Source https://jinja.palletsprojects.com/en/2.11.x/templates/#other-operators
!=: Compares two objects for inequality.
Source https://jinja.palletsprojects.com/en/2.11.x/templates/#comparisons
But since there are a true() and false() tests defined in Jinja, you could indeed write something like:
when: stat_result.stat.exists is not true()
And so a shorter version would be
when: stat_result.stat.exists is not true
# because the test function receives no parameters
Now indeed this test is similar to
when: stat_result.stat.exists != True
At the exception that the is not true test is more robust and would cope with undefined variables better than != True
For example, if you do comment your stat task, the test
when: stat_result.stat.exists is not true
would succeed, while
when: stat_result.stat.exists != True
would raise a fatal error:
The conditional check 'stat_result.stat.exists != True' failed. The error was: error while evaluating conditional (stat_result.stat.exists != True): 'stat_result' is undefined
Now this test is really not optimal, because you should not do things like when: bool_var is not true you should rater do when: not bool_var, so:
when: not stat_result.stat.exists
Consider this playbook:
---
- name: test
hosts: localhost
gather_facts: no
tasks:
- name: foo
shell: echo foo # skipped when running in check mode
register: result
- debug: msg="foo"
when: (result is defined)
and (result.stdout == 'foo')
I thought the is defined would result in short circuit evaluation as in other languages, but it doesn't.
If this is run in check mode, I get:
fatal: [localhost]: FAILED! => {"msg": "The task includes an option with an undefined variable. The error was: 'dict object' has no attribute 'stdout'
I know I can ignore the error with ignore_errors: "{{ansible_check_mode}}", but I want to learn how to fix this problem.
How do I rewrite the when clause to prevent referencing undefined variables?
Actually, if you debug the var without a condition, you will see it is defined. It simply does not contain a stdout key since the task was skipped. The correct ways to work arround this (non exhaustive list):
- debug: msg="{{ result.stdout | default('no value') }}
when: result.stdout | default('') == 'foo'
- debug: msg="foo"
when:
- result is not skipped
- result.stdout == 'foo'
Note that since your above shell example is not changing anything on the remote target you can also decide to play it even when running in check mode:
- name: foo
shell: echo foo
check_mode: false
changed_when: false
register: result
- debug: msg="foo"
when: result.stdout == 'foo'
I have a playbook (CIS compliance standard) with multiple tasks and I want to produce a "success" or "failed" depending on the ansible return code.
---
- name: 2.2.# Ensure ### Server is not enabled
block:
- name: Check if ### exists
stat: path=/usr/lib/systemd/system/###.service
register: exists
- name: Disable if exists
service:
name: ###
state: stopped
enabled: no
when: exists.stat.exists
register: result
- name: To File
block:
- name: Success
lineinfile:
dest: ./results/{{ customer }}-{{ scan_type }}-{{ inventory_hostname }}.txt
line: "{{ inventory_hostname }} 2.2.9 success"
insertafter: EOF
delegate_to: localhost
check_mode: False
when: ((result is skipped) or (result.enabled == false))
- name: Failed
lineinfile:
dest: ./results/{{ customer }}-{{ scan_type }}-{{ inventory_hostname }}.txt
line: "{{ inventory_hostname }} 2.2.9 failed"
insertafter: EOF
delegate_to: localhost
check_mode: False
when: ((result is not skipped) or (result.enabled == true))
From my observation, 'result' can have two different outputs depending on if the "Disable if exists" block is triggered.
If it is triggered, it'll give an output based on the "service" module.
If it is skipped, it'll give the generic Ansible output.
I'm fine with that, but what I can't seem to work out is the conditional statement.
when: ((result is not skipped) or (result.enabled == true))
This will always try to resolve both options, so if the module triggers, it will fail because "skipped" is not an attribute of the service module. If it skips, it'll pass, but obviously fail if it ever gets triggered. It's like it wants all conditions to exist before evaluating despite the "or" statement.
What am I doing wrong?
Do you mean result is skipped rather than result is not skipped? In any case, you can solve this using the default filter, which provides a default value if the input expression is undefined. For example:
when: result.enabled|default(false) == true
Of course, since that's a boolean, you can further simplify it to:
when: result.enabled|default(false)
can someone explain why the following happens:
I am trying to re-use the same variable (output) in two different tasks, of which only one task will run depending on a conditional (bool). I don't understand why when the following playbook is run, the debug hi task results in an error.
The echo hello and debug hello run and work as expected and then the echo hi is skipped (which is also expected as bool == True) but I don't understand why the debug hi results in an error. The error I get is:
The conditional check output.stdout == hi failed. The error was: error while evaluating conditional: output 'dict object' has no attribute stdout
Why doesn't the the output variable persist through the debug hi task? It's almost as if it is losing its value when debug hi is skipped.
- hosts: localhost
vars:
bool: True
tasks:
- name: echo hello
shell: echo "hello"
register: output
when: bool == True
- name: debug hello
debug:
msg: "hello"
when: output.stdout == "hello"
- name: echo hi
shell: echo "hi"
register: output
when: bool != True
- name: debug hi
debug:
msg: "hi"
when: output.stdout == "hi"
You are still registering the task result to output, even though the task was skipped. Take these very simple tasks for example:
- command: echo Hello
register: result
when: false
- debug:
var: result
The variable result is still created, and has the following contents:
"result": {
"changed": false,
"skip_reason": "Conditional result was False",
"skipped": true
}
This is still very useful information (i.e. to know that a task was skipped). You will need to account for this logic in your plays.
This task:
- name: echo hi
shell: echo "hi"
register: output
when: bool != True
registers output whether bool is true or not.
See the ansible documentation.
If a task fails or is skipped, the variable still is registered with a failure or skipped status, the only way to avoid registering a variable is using tags.
You can replace your latest when: condition with:
when: output.stdout is defined and output.stdout == "hi"