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'
Related
I want to assert network configuration with our inventory using a single assert task.
- name: Get running class-map config
vars:
ansible_connection: network_cli
ios_command:
commands:
- 'sh run | in class-map match-any'
register: show_policy
- name: Render template to variable
set_fact:
tem_var: "{{ lookup( 'template', 'test_policy.j2' ).splitlines() }}"
- name: Validate running class-map with acl
ansible.builtin.assert:
that:
- "'{{item}}' in show_policy.stdout_lines[0]"
success_msg: "TEST: {{ UNIT_HOSTNAME }}: VALIDATE RUNNING POLICY: PASSED"
fail_msg: "TEST: {{ UNIT_HOSTNAME }}: VALIDATE RUNNING POLICY: FAILED"
loop: "{{ tem_var }}"
Above assert works fine, but the configuration on the router sometimes holds more configuration than what is expected. I would like to use the same assert task as I am already using. So above assert should be used to also compare the 2 variables
"tem_var" is a variable generated from out inventory files.
Our inventory has for example 8 configuration lines, and the router has 9.
In that case I wan't the assert to fail, if the number of lines in each variable "show_policy" and "tem_var" is not the same.
I have done it succesfully with 2 additional tasks, but it would be nice if the assert module could fail instead.
This is working, but it adds up the number of tasks running:
- name: Consistency check running
set_fact: diff_list="{{ show_policy.stdout_lines[0] | difference(tem_var) }}"
- name: CONSISTENCY CHECK FAILED !! Device has unvalidated config
ansible.builtin.fail:
msg: "CONSISTENCY FAILURE: Please remove {{ diff_list }} from {{ UNIT_HOSTNAME }}"
when: diff_list|default("")|length > 0
How to assert failure when source list holds more values than target list?
According your current question headline and current description
Our inventory has for example 8 configuration lines, and the router has 9.
In that case I wan't the assert to fail, if the number of lines in each variable show_policy and tem_var is not the same.
you would need just to compare the length of both lists.
The minimal example playbook
---
- hosts: localhost
become: false
gather_facts: false
vars:
show_policy:
stdout_lines:
- '1'
- '2'
tem_var:
stdout_lines:
- '1'
- '2'
- '3'
tasks:
- name: Assert failure
ansible.builtin.assert:
that: # the number of lines in each variable is the same
- show_policy.stdout_lines | length == tem_var.stdout_lines | length
success_msg: "VALIDATE RUNNING POLICY: PASSED"
fail_msg: "VALIDATE RUNNING POLICY: FAILED"
will result into an output of
TASK [Assert failure] **********************************************************
fatal: [localhost]: FAILED! => changed=false
assertion: show_policy.stdout_lines | length == tem_var.stdout_lines | length
evaluated_to: false
msg: 'VALIDATE RUNNING POLICY: FAILED'
TASK [Assert failure] **********************************************************
ok: [localhost] => changed=false
msg: 'VALIDATE RUNNING POLICY: PASSED'
depending on the list length.
Further Documentation
assert module – Asserts given expressions are true
Jinja2 Template Designer Documentation - List of Builtin Filters
jinja-filters.length
Follow Ups
If you are interested in the difference between the configuration (list), in example if settings are missing
Ansible: How to compare two list variables?
How to compare lists in Ansible?
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)
I am writing a playbook that gives me the status of list of processes in loop but the output is not coming a desired
I am using ansible 2.7.1
---
- hosts: test_group
gather_facts: false
tasks:
- name: checking status
shell: /etc/init.d/{{ item }} status
register: output
loop:
- gdac
- scac
- name : print status
debug:
msg: "{{ item.stdout }}"
loop: "{{ output.results }}"
expecting output like (which given me the stdout or stdout_lines from the registered variable.
"msg":"Poller is Running\nSpooler is Running"
"msg": scac.db1: 3 of 3 running ( 7067 7060 7040 )\nayld.db1: 1 of 1 running ( 7114 )\nscac.db2: 3 of 3 running ( 7227 7216 7203 )\nayld.db2: 0 of 1 running
but am getting error
fatal: [test01]: FAILED! => {"msg": "The task includes an option with an undefined variable. The error was: 'item' is undefined
``
In your second task, you declared loop as an option of the debug module.
It should be an option of the task, not of the module. Since there is no loop declaration for the task, item is undefined.
You just have to fix your indentation:
- name : print status
debug:
msg: "{{ item.stdout }}"
loop: "{{ output.results }}"
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"
This is my code or checking wheter the website is valid and running:
- hosts: master
become: true
tasks:
- name: "Checking server availibility"
uri:
url: http://www.google.pl
timeout: 5
register: command_result
ignore_errors: yes
- debug: msg= "{{ command_result }}"
So command_result variable always returns this output regardless of the website availability:
ok: [Centos.v1] => {
"changed": false,
"msg": "Hello world!"
}
I would like to know if the first task was succesful or not but i can't do that when the output of registered variable is always the same.
How to setup my variable properly to read the return code?
and also
Is there some other way to check if an error occured in the previous task?
This has nothing to do with registering.
Remove the space character after msg=, otherwise you pass an empty string to the debug module and it prints Hello World! as a result (its default behaviour).
Better yet, use var parameter and YAML syntax:
- debug:
var: command_result