Check if arrays are defined and not empty in ansible - ansible

I have the following code
- set_fact:
MY_HOSTNAME: "SOME VALUE"
MY_SERVER: "00.00.00.00"
- name: Get MY server
set_fact:
MY_SERVER: "{{ groups[MY_HOSTNAME][0] }}"
when: groups[MY_HOSTNAME] is defined
In the above code, groups[MY_HOSTNAME] is an array. What is the best way to check that groups[MY_HOSTNAME] is defined and also that it is not empty
If it is either of that I want the value 00.00.00.00 to be assigned to MY_SERVER

I don't know if it's version specific, but I'm currently running ansible-2.3.2 on RHEL6 and I had to put quotes around the group name to get it to work for me:
when: groups["GROUP_NAME"] is defined and (groups["GROUP_NAME"]|length>0)
Edit: I couldn't add this as a comment to techraf's answer because I don't have enough reputation.

list | length filter returns the number of elements. If it is zero, the list is empty.
For a conditional value use if or ternary filter (example in this answer).
For a composite conditional (groups[MY_HOSTNAME]| default([])) | length.

You can first check if it is indeed a list and then check if it has more than one element:
when: (groups['MY_HOSTNAME'] is defined) and (groups['MY_HOSTNAME'] | type_debug == 'list') and (groups['MY_HOSTNAME'] | length > 0)

try this one (it checks if variable is defined and if it is a iterable like list)
when: (groups["GROUP_NAME"] is defined) and (groups["GROUP_NAME"] is iterable)

Related

Ansible: How to check a value: key pair in the when condition

First of all, I have separated my playbooks in this way:
environtments/something/host_vars/host1.yml
roles/sshd/tasks/main.yml
The host's environment variable contains some value: key pairs, in this case:
specialinfo: 'blah'
And the role contains a block, which should be run when this 'specialinfo' value matches to blah:
- name: Check that specialvalue is blah
block:
- name: Do something
...
- name: Do something else
...
when: {{ specialinfo }} == 'blah'
Please help me to achive this, because I'm quite sure that the problem is inside the 'when' condition. If I need a copy (or something like that) inside a name, I can use {{ specialinfo }}
Please also notice that I can't use with_items here, because it can't be used inside the block element.
First of all, you can't use double curly braces in when because the condition itself needs to be a jinja expression. Change the condition to when: specialinfo == 'blah'.
Note that, when condition for block applies to all the tasks inside the block. This means that the same ... == 'blah' condition will be applied to both Do something and Do something else tasks. If you want to control when a particular task to be executed, use when for the task alone like so:
- debug: Do something
msg: is blah
when: specialinfo == 'blah'
- debug: Do something else
msg: is not blah
when: specialinfo != 'blah'

Ansible when condition always fails when a dictionary is there but it contains no elements

The problem that I am facing is the following:
I have the following lines in my vars:
testparams:
- { name: 'test' , test_type: 'test' }
What I need to do, from my tasks, is to include a file when this dictionary is defined. This is my code:
- include: test.yml
when: testparams is defined
It works when the whole definition of the dictionary is absent. But, when the dictionary definition is there but without any elements inside, this condition fails. I have tried with length, trying to get the type(just run it when it's Dictionary), but everything has failed so far.
Example of defined dictionary without elements:
testparams:
Any ideas?
The definition of an empty list is done with brackets like in the following example:
testparams: []
And in your case, I would use the following test to have it work in all conditions:
when: testparams | default([]) | length > 0
Be aware that you are still responsible to sanitize your testparams definition as this might break if:
you define a list of testparams that does not have the correct values
you define testparams as a non empty string (which also has a length > 0).

Ansible - 'Undefined' at beginning and end of Key when map'ing a Attribute

When map'ing a attribute in a list of variables, Ansible is adding a 'Undefined' to the beginning and end of the key.
The variables:
vault_config_listener_params:
- address: "0.0.0.0:8200"
- tls_cert_file: "/etc/ssl/certs/wildcard.crt"
- tls_key_file: "/etc/ssl/certs/wildcard.key"
The debug task:
- debug: var=vault_config_listener_params|map(attribute="tls_cert_file")|list
The output:
ok: [id70118] => {
"vault_config_listener_params|map(attribute=\"tls_cert_file\")|list":
"[Undefined, u'/etc/ssl/certs/wildcard.crt', Undefined]"
}
The maping seems to have worked, as the key path has been extracted. But where are the 'Undefined' coming from?
PS: The variables needs to be a list, as they are looped it in another place with jinja2.
First of all, don't use debug's var when printing arbitrary expressions, use msg instead.
As for your question, map is quite dumb and doesn't do what you don't ask it to, so you actually need to select items with specified attributes defined first, and then get its values:
- debug:
msg: "{{ vault_config_listener_params | selectattr('tls_cert_file','defined') | map(attribute='tls_cert_file') | list }}"

Ansible, set_fact using if then else statement

I am trying to set a variable in Ansible with set_fact at runtime based upon another variable. If uses first value no matter what the actual value is. Here is my code example:
- name: Global_vars - get date info
set_fact:
jm_env: "{{lookup('env', 'Environment')}}"
l_env: "{% if '{{jm_env}}==Develop' %}d{% elif '{{jm_env}}==Staging'%}s{% else %}p{% endif %}"
l_env is d no matter what jm_env is set.
Firstly, dictionaries in YAML are not ordered (and the syntax used by Ansible here is a YAML dictionary), so you have no guarantee Ansible would first set jm_env before proceeding to l_env -- you need to split the assignment into two tasks.
Secondly, your test expressions are incorrect -- '{{jm_env}}==Develop' is a string because it is quoted; and testing if 'string' will always evaluate to true (this is the direct reason you always get d in the output).
Use:
- name: Set the jm_env
set_fact:
jm_env: "{{lookup('env', 'Environment')}}"
- name: Set the l_env
set_fact:
l_env: "{% if jm_env=='Develop' %}d{% elif jm_env=='Staging'%}s{% else %}p{% endif %}"
One of the simple way to set fact based condition example as follows:
- name: Set facts for delete operation results
set_fact:
tr_result: "{{ '{\"status\": \"SUCCESS\"}' if (op_result['output'] == 'Deleted') else '{\"status\" : \"FAILED\"}' }}"
Note: Assume op_result is a dict & already defined.
Code has been tested and working well.

Ansible - Find max value and run action based on a result only on one host

I have a group of hosts named "db" with number of nodes that may vary. Each node has a fact ("seqno") which is an integer number.
I need to compare this fact among all hosts and choose maximum value, then run some actions on one (and only one) host that has this maximum value. In case of multiple nodes having the same value, first node should be chosen.
I tried this approach:
- name: find max seqno value
set_fact: seqno_max={{ [hostvars[groups['db'][0]]['seqno'], hostvars[groups['db'][1]]['seqno']] | max }}
- name: find single hostname to use as a node with max seqno
set_fact: seqno_max_host={{ hostvars[item]['inventory_hostname'] }}
with_items: groups['db'][::-1] # reverse list. if two nodes have the same seqno, use first node as starting point.
when: hostvars[item]['seqno'] == seqno_max
- name: Some actions based on a result of previous tasks
action: # Run some actions
when: seqno_max_host == inventory_hostname
But for some reason "max" operator always return the second value. Also this approach is valid only if you have arbitrary specified number of hosts - I would like to have a solution that works for any number of hosts.
EDIT: It turned out that hostvars converted integer back to string, so comparison of them gave unexpected results. Reapplying int filter for each hostvars reference inside max filter helped. Still, questions remains how to fix code above to make it work for any number of hosts - is it possible without writing custom filters or creating temporary templates?
I ended up with a solution which is not beautiful, but works.
- shell: "if [ {{ hostvars[inventory_hostname]['seqno'] }} -lt {{ hostvars[item]['seqno'] }} ]; then echo {{ hostvars[item]['seqno'] }}; fi"
with_items: groups['db']
register: result_c
- set_fact: seqno_max={{ hostvars[inventory_hostname]['seqno'] }}
when: result_c.results | map(attribute='stdout') | join('') == ""

Resources