ansible: set fact with multiple values [duplicate] - ansible

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.

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 test for variable use another is not defined

I use the ternary operator to return a variable if it is defined, otherwise another one:
{{ (variable1 is defined) | ternary(variable1, variable2) }}
It's a bit clumsy. Is there a better way to do this?
Try this one
- hosts: nodes
gather_facts: false
vars:
var1: value1
tasks:
- name: Show 1
debug: msg="{{ var1 | default('AAAAAA') }}"
- name: Show 2
debug: msg="{{ var2 | default('BBBBBB') }}"

How can I save values into a list var from multiple tasks in ansible?

I have a playbook where I execute several tasks. Each task can be executed if it meets the WHEN condition. I would like to save some data into a list so I can use it later in the process.
Here is an over simplified example to illustrate my need:
- Set GlobalVar = []
- task A
when task_A_enabled
register custom_value_A into GlobalVar
- task B
when task_B_enabled
register custom_value_B into GlobalVar
- task C
do something with GlobalVar
I hope it's clear enough to help me figure out how to do that. Thank you.
An option would be to use block
For example the play below
- hosts: localhost
gather_facts: no
vars:
GlobalVar: []
task_a: true
task_b: false
tasks:
- name: task A
block:
- debug:
msg: Task A is enabled
- set_fact:
GlobalVar: "{{ GlobalVar + [ 'A' ] }}"
when: task_a
- name: task B
block:
- debug:
msg: Task B is enabled
- set_fact:
GlobalVar: "{{ GlobalVar + [ 'B' ] }}"
when: task_b
- name: task C
debug:
var: GlobalVar
gives (abridged):
ok: [localhost] => {
"msg": "Task A is enabled"
}
...
ok: [localhost] => {
"GlobalVar": [
"A"
]
}
You can use the module set_fact to do the variable assignment and use blocks to group a task and the variable assignment step so you can check conditions once:
---
- hosts: "all"
vars:
GlobalVar: []
tasks:
- block:
- set_fact:
GlobalVar: "{{ GlobalVar + [1, 2] }}"
- debug:
msg: "{{GlobalVar}}"
when: true
- block:
- set_fact:
GlobalVar: "{{ GlobalVar + [3, 4] }}"
- debug:
msg: "{{GlobalVar}}"
when: false
- block:
- set_fact:
GlobalVar: "{{ GlobalVar + [5, 6] }}"
- debug:
msg: "{{GlobalVar}}"
when: true

How to write varibles/hard code values in nested json in ansible?

I'm trying to create a json file with hard codes valuesas a output in nested json.But the second play is overwriting the first play value.So do we have any best option to do this?
I have tried with to_nice_json template to copy the variable to json file.But not able to keep multiple variable values in imported_var to copy to json file
---
- hosts: localhost
connection: local
gather_facts: false
tasks:
- name: load var from file
include_vars:
file: /tmp/var.json
name: imported_var
- name: Checking mysqld status
shell: service mysqld status
register: mysqld_stat
ignore_errors: true
- name: Checking mysqld status
shell: service httpd status
register: httpd_stat
ignore_errors: true
- name: append mysqld status to output json
set_fact:
imported_var: "{{ imported_var | combine({ 'status_checks':[{'mysqld_status': (mysqld_stat.rc == 0)|ternary('good', 'bad') }]})}}"
# - name: write var to file
# copy:
# content: "{{ imported_var | to_nice_json }}"
# dest: /tmp/final.json
- name: append httpd status to output json
set_fact:
imported_var: "{{ imported_var| combine({ 'status_checks':[{'httpd_status': (httpd_stat.rc == 0)|ternary('good', 'bad') }]})}}"
# - debug:
# var: imported_var
- name: write var to file
copy:
content: "{{ imported_var | to_nice_json }}"
dest: /tmp/final.json
Expected result:
{
"status_checks": [
{
"mysqld_status": "good"
"httpd_status": "good"
}
]
}
Actual result:
{
"status_checks": [
{
"httpd_status": "good"
}
]
}
You're trying to perform the sort of data manipulation that Ansible really isn't all that good at. Any time you attempt to modify an existing variable -- especially if you're trying to set a nested value -- you're making life complicated. Having said that, it is possible to do what you want. For example:
---
- hosts: localhost
gather_facts: false
vars:
imported_var: {}
tasks:
- name: Checking sshd status
command: systemctl is-active sshd
register: sshd_stat
ignore_errors: true
- name: Checking httpd status
command: systemctl is-active httpd
register: httpd_stat
ignore_errors: true
- set_fact:
imported_var: "{{ imported_var|combine({'status_checks': []}) }}"
- set_fact:
imported_var: >-
{{ imported_var|combine({'status_checks':
imported_var.status_checks + [{'sshd_status': (sshd_stat.rc == 0)|ternary('good', 'bad')}]}) }}
- set_fact:
imported_var: >-
{{ imported_var|combine({'status_checks':
imported_var.status_checks + [{'httpd_status': (httpd_stat.rc == 0)|ternary('good', 'bad')}]}) }}
- debug:
var: imported_var
On my system (which is running sshd but is not running httpd, this will output:
TASK [debug] **********************************************************************************
ok: [localhost] => {
"imported_var": {
"status_checks": [
{
"sshd_status": "good"
},
{
"httpd_status": "bad"
}
]
}
}
You could dramatically simplify the playbook by restructuring your data. Make status_checks a top level variable, and instead of having it be a list, have it be a dictionary that maps a service name to the corresponding status. Combine this with some loops and you end up with something that is dramatically simpler:
---
- hosts: localhost
gather_facts: false
tasks:
# We can use a loop here instead of writing a separate task
# for each service.
- name: Checking service status
command: systemctl is-active {{ item }}
register: services
ignore_errors: true
loop:
- sshd
- httpd
# Using a loop in the previous task means we can use a loop
# when creating the status_checks variable, which again removes
# a bunch of duplicate code.
- name: set status_checks variable
set_fact:
status_checks: "{{ status_checks|default({})|combine({item.item: (item.rc == 0)|ternary('good', 'bad')}) }}"
loop: "{{ services.results }}"
- debug:
var: status_checks
The above will output:
TASK [debug] **********************************************************************************************************************************************************************************
ok: [localhost] => {
"status_checks": {
"httpd": "bad",
"sshd": "good"
}
}
If you really want to add this information to your imported_var, you can do that in a single task:
- set_fact:
imported_var: "{{ imported_var|combine({'status_checks': status_checks}) }}"

Ansible - How to keep appending new keys to a dictionary when using set_fact module with with_items?

I want to add keys to a dictionary when using set_fact with with_items. This is a small POC which will help me complete some other work. I have tried to generalize the POC so as to remove all the irrelevant details from it.
When I execute following code it is shows a dictionary with only one key that corresponds to the last item of the with_items. It seems that it is re-creating a new dictionary or may be overriding an existing dictionary for every item in the with_items. I want a single dictionary with all the keys.
Code:
---
- hosts: localhost
connection: local
vars:
some_value: 12345
dict: {}
tasks:
- set_fact: {
dict: "{
{{ item }}: {{ some_value }}
}"
}
with_items:
- 1
- 2
- 3
- debug: msg="{{ dict }}"
This can also be done without resorting to plugins, tested in Ansible 2.2.
---
- hosts: localhost
connection: local
vars:
some_value: 12345
dict: {}
tasks:
- set_fact:
dict: "{{ dict | combine( { item: some_value } ) }}"
with_items:
- 1
- 2
- 3
- debug: msg="{{ dict }}"
Alternatively, this can be written without the complex one-liner with an include file.
tasks:
- include: append_dict.yml
with_items: [1, 2, 3]
append_dict.yml:
- name: "Append dict: define helper variable"
set_fact:
_append_dict: "{ '{{ item }}': {{ some_value }} }"
- name: "Append dict: execute append"
set_fact:
dict: "{{ dict | combine( _append_dict ) }}"
Output:
TASK [debug]
*******************************************************************
ok: [localhost] => {
"msg": {
"1": "12345",
"2": "12345",
"3": "12345"
}
}
Single quotes ' around {{ some_value }} are needed to store string values explicitly.
This syntax can also be used to append from a dict elementwise using with_dict by referring to item.key and item.value.
Manipulations like adding pre- and postfixes or hashes can be performed in the same step, for example
set_fact:
dict: "{{ dict | combine( { item.key + key_postfix: item.value + '_' + item.value | hash('md5') } ) }}"
Use a filter plugin.
First, make a new file in your ansible base dir called filter_plugins/makedict.py.
Now create a new function called "makedict" (or whatever you want) that takes a value and a list and returns a new dictionary where the keys are the elements of the list and the value is always the same.
class FilterModule(object):
def filters(self):
return { 'makedict': lambda _val, _list: { k: _val for k in _list } }
Now you can use the new filter in the playbook to achieve your desired result:
- hosts: 127.0.0.1
connection: local
vars:
my_value: 12345
my_keys: [1, 2, 3]
tasks:
- set_fact: my_dict="{{ my_value | makedict(my_keys) }}"
- debug: msg="{{ item.key }}={{ item.value }}"
with_dict: "{{my_dict}}"
You can customize the location of the filter plugin using the filter_plugins option in ansible.cfg.
this does not seems to work any more on ansible 2.5
---
- hosts: localhost
connection: local
vars:
some_value: 12345
dict: {}
tasks:
- set_fact:
dict: "{{ dict | combine( { item: some_value } ) }}"
with_items:
- 1
- 2
- 3
- debug: msg="{{ dict }}"
returns only last value {"dict":{"3": "some value"}}
I suggest you could do this :
- set_fact:
__dict: |
{% for item in [1,2,3] %}
{{item}}: "value"
{% endfor %}
- set_fact:
final_dict: "{{__dict|from_yaml}}"
- debug:
var: final_dict
Another solution could be this one, tested in Ansible 2.9.6.
This solutions adds the extra benefit that you do not have to declare _dict beforehand in vars section. This is achieved by the | default({}) pipe which ensures that the loop will not fail in the first iteration when _dict is empty.
In addition, the renaming of dict to _dict is necessary since the dict is a special keyword reserved for <class 'dict'>. Referenced (unfortunately only at devel branch yet) here.
---
- hosts: localhost
connection: local
vars:
some_value: 12345
tasks:
- set_fact:
_dict: "{{ _dict | default({}) | combine( { item: some_value } ) }}"
with_items:
- 1
- 2
- 3
- debug: msg="{{ _dict }}"

Resources