Remove empty element from list in ansible - ansible

input:
- tests_to_run
tasks:
modify_user_input:
< Remove the empty elements from tests_to_run>
Have been searching for hours. Does anyone know a way to filter the input, so I can manipulate the tests_to_run list, so it does not have any empty elements.
Sample input and output
Sample tests_to_run = ['test_a','','test_b','test_c','yrls']
Expected variable to be used in later tasks test_to_run_clean = ['test_a','test_b','test_c','yrls']

To remove the empty element from the list:
---
- name: Sample playbook
connection: local
gather_facts: false
hosts: localhost
vars:
mylist:
- abc
- def
-
- jkl
tasks:
- set_fact:
mylist: "{{ mylist|select()|list }}"

Related

Ansibe: concatenation of items from with_items

I'm trying to get a variable which will contain comma separated items from with_itmes loop as follow:
- hosts: localhost
connection: local
gather_facts: no
tasks:
- name: set_fact
set_fact:
foo: "{{ foo }}{{ item }},"
with_items:
- "one"
- "two"
- "three"
vars:
foo: ""
- name: Print the var
debug:
var: foo
It works as expected but what I'm getting at the end is trailing comma.
Is there any way to remove it?
There is a join filter that we can use with lists to concatenate list elements with a given character.
If we are passing the list directly to with_items or loop then we can use loop_control to "extend" some more loop information to get ansible_loop.allitems. Then this can be joined with the join filter.
Example:
- set_fact:
foo: "{{ ansible_loop.allitems|join(',') }}"
loop:
- one
- two
- three
loop_control:
extended: true
Otherwise a more straightforward way is to define a variable with list and use join filter on elements of that variable.
Example:
- set_fact:
foo: "{{ mylist|join(',') }}"
vars:
mylist:
- one
- two
- three
No clue if this is correct way to do but it does the job:
- name: Print the var
debug:
msg: "LIST: {{ foo | regex_replace(',$','') }}"

ansible: set fact with multiple values [duplicate]

I would like to add an item to a list in ansible dependent on some condition being met.
This doesn't work:
some_dictionary:
app:
- something
- something else
- something conditional # only want this item when some_condition == True
when: some_condition
I am not sure of the correct way to do this. Can I create a new task to add to the app value in the some_dictionary somehow?
You can filter out all falsey values with select(), but remember to apply the list() filter afterwards. This seems an easier and more readable approach for me:
- name: Test
hosts: localhost
gather_facts: no
vars:
mylist:
- "{{ (true) | ternary('a','') }}"
- "{{ (false) | ternary('b','') }}"
- "{{ (true) | ternary('c','') }}"
tasks:
- debug:
var: mylist|select|list
Result:
TASK [debug] *****************************************************************************************************************************************************************************************************************
ok: [localhost] => {
"mylist|select()|list": [
"a",
"c"
]
}
Replace (true) and (false) with whatever test you want.
Is there a reason you have to do everything in one go?
This is pretty easy if you specify the additional item(s) to add in separate vars, as you can just do list1 + list2.
---
- hosts: localhost
gather_facts: False
connection: local
vars:
mylist:
- one
- two
mycondition: False
myconditionalitem: foo
tasks:
- debug:
msg: "{{ mylist + [myconditionalitem] if mycondition else mylist }}"
I'd try to avoid this, but if conditional list is absolutely necessary, you can use this trick:
---
- hosts: localhost
gather_facts: no
vars:
a: 1
b: 1
c: 2
some_dictionary:
app: "{{ '[\"something\", \"something else\"' + (a + b == c) | ternary(', \"something conditional\"',' ') + ']' }}"
tasks:
- debug: var=some_dictionary.app
It will form an array-like string (["item1","item2","item3"]) and ansible variable templator will convert it into list before assigning to app.
Based on Konstantin's solution I developed the following:
- hosts: localhost
gather_facts: no
vars:
a: "{{ True if var1|d(True) else False }}"
b: "{{ True if var2|d(False) else False }}"
n: "{{ True if var2|d(True) else False }}"
some_list: "{{ '[' +
a|ternary('\"item1\",',' ') +
b|ternary('\"item2\",',' ') +
n|ternary('\"itemN\",',' ') + ']' }}"
tasks:
- debug: var=some_list
This will create a list with items "item1" till "itemN", but each item is only appended if the corresponding flag expands to 'True'.
Hope, this helps.

Running a playbook on multiple host groups one at a time

I want to run a playbook containing some roles on multiple host groups I create dynamically with the group_by module.
I'm able to do it like the example below (ping replacing my actual role).
I was wondering if there is a way to run each group separately in a loop instead of listing all instance ids. I don't want to create a duplicate line with every instance id.
The purpose here is to deploy to one instance in every data center at a time instead of running all with a low serial which takes a long time.
There might be a different way of doing it, I don't want to create static groups in the inventory for each instance_id as well.
---
- hosts: tag_type_edgenode
tasks:
- group_by: key=instance_id_{{instance_id}}
register: dyn_groups
- hosts: instance_id_1
tasks:
- ping:
- hosts: instance_id_2
tasks:
- ping:
- hosts: instance_id_3
tasks:
- ping:
- hosts: instance_id_4
tasks:
- ping:
If you have equal count of hosts in each group, you can use pattern + serial.
Ansible forms host list by pattern moving through groups sequentially. So if you have equal count of hosts, then batches formed by serial will be equal to groups.
In your example, if you have exactly 3 hosts in each group, you can use:
- hosts: instance_id_*
serial: 3
tasks:
- ping:
If you don't mind a bit of Ansible patching, you can modify _get_serialized_batches method.
Add this code just before while len(all_hosts) > 0::
if 'serialize_by_var' in play.get_vars():
param = play.get_vars()['serialize_by_var']
sb = []
def by_param(x):
vrs = x.get_vars()
if param in vrs:
return vrs[param]
else:
return None
s_hosts = sorted(all_hosts,key=by_param)
for k, g in itertools.groupby(s_hosts, by_param):
sb.append(list(g))
display.vv('Serializing by host var "{}": {}'.format(param,sb))
return sb
And you can serialize hosts by any variable like this:
- hosts: tag_type_edgenode
vars:
serialize_by_var: instance_id
tasks:
- ping
There is a simpler method using the size (length) of each group. However, this runs on all members of a group, flowing through the groups sequentially. I think OP was asking for how to act on the first member of each group, which I am also working on figuring out.
- name: "Restore selected devices in model_dc"
hosts: group_1, group_2, group_3, group_4, group_5, group_6, group_7, !excluded
any_errors_fatal: true # Stop entire play if one host fails
gather_facts: no
order: inventory
serial:
- "{{ groups['group_1'] | length }}" # The number of hosts for first batch
- "{{ groups['group_2'] | length }}" # The number of hosts for second batch
- "{{ groups['group_3'] | length }}"
- "{{ groups['group_4'] | length }}"
- "{{ groups['group_5'] | length }}"
- "{{ groups['group_6'] | length }}"
- "{{ groups['group_7'] | length }}" # The number of hosts for every batch until the end.
Building off of Konstantin's idea, you can do something like this using aliases and a list of patterns:
---
- hosts: "*-server-batch-1,*-servers-batch-2,*-server-batch-3"
serial: 3
...
...
[london]
london-server-batch-1 ansible_host=server1.london.com
london-server-batch-2 ansible_host=server2.london.com
london-server-batch-3 ansible_host=server3.london.com
[tokyo]
tokyo-server-batch-1 ansible_host=server1.tokyo.com
tokyo-server-batch-2 ansible_host=server2.tokyo.com
tokyo-server-batch-3 ansible_host=server3.tokyo.com

Registering multiple variables in a loop

I have a variable yaml file:
---
apps:
- client
- node
- gateway
- ic
- consul
and this task:
- name: register if apps exists
stat: path="/etc/init.d/{{ item }}"
with_items: apps
register: "{{ item.stat.exists }}exists"
I need to end up with a variable for each app with a value of true or false whether the file exists or not:
clientexists = true
nodeexists = true
gatewayexists = false
icexists = true
consulexists = false
For some reason, the item and exists concat is not working.
How can I achieve that??
Try this hope this will help you out. While looping in Stats.yml then msg field will contain your desired output.
Variables.yml Here we are defining variables
---
apps:
- client
- node
- gateway
- ic
- consul
Stats.yml
---
- hosts: localhost
name: Gathering facts
vars_files:
- /path/to/variables.yml
tasks:
- name: "Here we are printing variables"
debug:
msg: "{{apps}}"
- name: "Here we are gathering stats and registering it"
stat:
path: "/etc/init.d/{{item}}"
register: folder_stats
with_items:
- "{{apps}}"
- name: "Looping over registered variables, Its msg field will contain desired output"
debug:
msg: "{{item.invocation.module_args.path}}: {{item.stat.exists}}"
with_items:
- "{{folder_stats.results}}"
...
folder_stats will contain whole result, for individually referring to single - single result You can use it like this in you playbook.
folder_stats.results[0].stat.exists
folder_stats.results[1].stat.exists
folder_stats.results[2].stat.exists
folder_stats.results[3].stat.exists

How to assign a random number to a variable in ansible?

This is an ansible script that I was expecting to print out the same random number three times. Instead, it prints out three random numbers. How do I assign a random number to a variable in ansible so that it is fixed throughout the playbook?
---
- name: Test random filter
hosts: localhost
gather_facts: False
vars:
random_number: "{{ 100 | random }}"
tasks:
- name: Print the random number
debug: var=random_number
- name: Print the random number
debug: var=random_number
- name: Print the random number
debug: var=random_number
Just use the set_fact module as a task first:
- set_fact:
r: "{{ 100 | random }}"
run_once: yes
Subsequently, debug: msg=... has the value of r fixed.
Set facts under task:
---
- name: Test random filter
hosts: localhost
gather_facts: False
tasks:
- name: set fact here
set_fact:
randome_number: "{{ 100 | random }}"
run_once: yes
- name: Print the random number
debug: var=random_number
- name: Print the random number
debug: var=random_number
- name: Print the random number
debug: var=random_number

Resources