In Ansible, if I try to use a variable as a parameter name, or a key name, it is never resolved. For example, if I have {{ some_var }}: true, or:
template: "{{ resolve_me_to_src }}": "some_src"
the variables will just be used literally and never resolve. My specific use case is using this with the ec2 module, where some of my tag names are stored as variables:
- name: Provision a set of instances
ec2:
group: "{{ aws_security_group }}"
instance_type: "{{ aws_instance_type }}"
image: "{{ aws_ami_id }}"
region: "{{ aws_region }}"
vpc_subnet_id: "{{ aws_vpc_subnet_id }}"
key_name: "{{ aws_key_name }}"
wait: true
count: "{{ num_machines }}"
instance_tags: { "{{ some_tag }}": "{{ some_value }}", "{{ other_tag }}": "{{ other_value }}" }
Is there any way around this? Can I mark that I want to force evaluation somehow?
Will this work for you?
(rc=0)$ cat training.yml
- hosts: localhost
tags: so5
gather_facts: False
vars: [
k1: 'key1',
k2: 'key2',
d1: "{
'{{k1}}': 'value1',
'{{k2}}': 'value2',
}",
]
tasks:
- debug: msg="{{item}}"
with_dict: "{{d1}}"
(rc=0)$ ansible-playbook training.yml -t so5
PLAY [localhost] ****************************************************************
PLAY [localhost] ****************************************************************
TASK: [debug msg="{{item}}"] **************************************************
ok: [localhost] => (item={'key': 'key2', 'value': 'value2'}) => {
"item": {
"key": "key2",
"value": "value2"
},
"msg": "{'value': 'value2', 'key': 'key2'}"
}
ok: [localhost] => (item={'key': 'key1', 'value': 'value1'}) => {
"item": {
"key": "key1",
"value": "value1"
},
"msg": "{'value': 'value1', 'key': 'key1'}"
}
PLAY RECAP ********************************************************************
localhost : ok=1 changed=0 unreachable=0 failed=0
(rc=0)$
Trick is to wrap dict declaration with double quotes. Ansible applies this undocumented (but consistant) and crappy translation (ansible's equivalent of shell variable expantion) to most (not all) YAML values (everything RHS of ':') in the playbook. It is some combination putting these strings through Jinja2-engine, Python-interpreter and ansible-engine in some unknown order.
Another option - you can try something like:
module_name: "{{ item.key }}={{ item.value }}"
with_items:
- { key: "option", value: "{{ any_value }}" }
Please note that everything is inline and I'm using an equal (=) and everything is wrapped with double quotes.
Related
I have an ansible playbook in which I need to pass 2 metadata elements to 2 different variables.
My relevent code in my yml is:
- debug:
var: result
- name: convert
set_fact:
var1: "{{ result | map(attribute='appname') }}"
var2: "{{ result | map(attribute='vipport') }}"
My metadata output looks like this:
"result": {
"changed": true,
"failed": false,
"meta": {
"appname": " testserver4",
"serverquerytype": "A",
"servicemonitor": "http-ecv",
"serviceport": 4433,
"vipmethod": "LEASTCONNECTION",
"vipport": 80,
"viptype": "HTTP"
}
I need to be able to create a variable of appname and vipport, the code I tried above does not work. Any idea what I am missing?
Below I put examples of how you can manipulate the result variable. Set_fact is redundant but I have added and example of taking the variable directly from the result variable.
- hosts: localhost
vars:
"result": {
"changed": true,
"failed": false,
"meta": {
"appname": " testserver4",
"serverquerytype": "A",
"servicemonitor": "http-ecv",
"serviceport": 4433,
"vipmethod": "LEASTCONNECTION",
"vipport": 80,
"viptype": "HTTP"
}
}
tasks:
- name: set var1 and var2 from result var
set_fact:
var1: "{{ result.meta.appname }}"
var2: "{{ result.meta.vipport }}"
- name: output of set_fact
debug:
msg:
- "{{ var1 }}"
- "{{ var2 }}"
- name: Output directly from result variable. Just an example that set_fact is not really needed.
debug:
msg:
- "{{ result.meta.appname }}"
- "{{ result.meta.vipport }}"
Output:
TASK [set var1 and var2 from result var] ***************************************************************************************************************************************************
ok: [localhost]
TASK [output of set_fact] ******************************************************************************************************************************************************************
ok: [localhost] => {
"msg": [
" testserver4",
"80"
]
}
TASK [Output directly from result variable. Just an example that set_fact is not really needed.] *******************************************************************************************
ok: [localhost] => {
"msg": [
" testserver4",
"80"
]
}
I have a json file that contains a list of dictionaries:
$ cat test.json
[
{
"key1": "val1"
},
{
"key2": "val2"
},
{
"key3": "val3"
}
]
I would like to iterate over the list and print the key and value in each dict object but can't figure out a way to do so.
- hosts: localhost
gather_facts: no
tasks:
- set_fact:
mylst: "{{ lookup('file', '/home/ansible/test.json') | from_json }}"
- debug:
msg: "One dict is {{ item }}"
with_items: "{{ mylst }}"
- debug:
msg: "Key/values are {{ item.key }} and {{item.value }}"
with_items: "{{ mylst }}"
$ ansible-playbook run.yml
PLAY [localhost] *****************************************************************************
TASK [set_fact] ******************************************************************************
ok: [localhost]
TASK [debug] *********************************************************************************
ok: [localhost] => (item={u'key1': u'val1'}) => {
"msg": "One dict is {u'key1': u'val1'}"
}
ok: [localhost] => (item={u'key2': u'val2'}) => {
"msg": "One dict is {u'key2': u'val2'}"
}
ok: [localhost] => (item={u'key3': u'val3'}) => {
"msg": "One dict is {u'key3': u'val3'}"
}
TASK [debug] *********************************************************************************
fatal: [localhost]: FAILED! => {"msg": "The task includes an option with an undefined variable. The error was: 'dict object' has no attribute 'key'\n\nThe error appears to be in '/home/ansible/run.yml': line 11, column 5, but may\nbe elsewhere in the file depending on the exact syntax problem.\n\nThe offending line appears to be:\n\n\n - debug:\n ^ here\n"}
PLAY RECAP ***********************************************************************************
localhost : ok=2 changed=0 unreachable=0 failed=1 skipped=0 rescued=0 ignored=0
The lookup plugin below
mylst: "{{ lookup('file', 'test.json')|from_json }}"
creates a list of dictionaries
mylst:
- key1: val1
- key2: val2
- key3: val3
If the keys are unique combine the items
- debug:
var: mylst|combine
This creates a dictionary
mylst|combine:
key1: val1
key2: val2
key3: val3
Decompose the dictionaries and create the list
- debug:
var: mylst|combine|dict2items
gives
mylst|combine|dict2items:
- key: key1
value: val1
- key: key2
value: val2
- key: key3
value: val3
Now, you can easily iterate the list and print the key/value pairs of each dictionary
- debug:
msg: "key={{ item.key }}, value={{ item.value }}"
loop: "{{ mylst|combine|dict2items }}"
gives (abridged)
msg: key=key1, value=val1
msg: key=key2, value=val2
msg: key=key3, value=val3
Example of a complete playbook
- hosts: localhost
vars:
mylst: "{{ lookup('file', 'test.json')|from_json }}"
tasks:
- debug:
msg: "key={{ item.key }}, value={{ item.value }}"
loop: "{{ mylst|combine|dict2items }}"
I'm having trouble grasping data manipulation in Ansible. Here's the setup:
- hosts: emirr01
gather_facts: no
vars:
setup:
- emirr01: { label: "label1" }
- emirr02: { label: "label2" }
- emirr03: { label: "label3" }
lookup: [ "emirr01", "emirr02"]
use_labels: [ ]
tasks:
- debug: msg="setup={{ setup }}"
- debug: msg="lookup={{ lookup }}"
- debug: msg="item0={{ item.0 }} item1={{ item.1 }}"
when: inventory_hostname == item.1
with_nested:
- "{{ setup }}"
- "{{ lookup }}"
- set_fact:
use_labels: "{{ use_labels + [ item.1.label ] }}"
when: item.0 == item.1.label
with_nested:
- "{{ setup }}"
- "{{ lookup }}"
- debug: msg="use_labels={{ use_labels }}"
What I need is to set a fact use_labels which is a list of labels as defined in setup list for each host found in lookup list. What I expect is a list like this:
[ "label1", "label2" ]
My problem is not being able to "reach" label in a list I get in item.1 which is (example):
item1=[{'emirr02': {'label': 'label2'}}
Here's is the error:
$ ansible-playbook test.yml -l emirr01
PLAY [emirr01] ****************************************************************************************************************************************************************************************************************************************************************************************************************************
TASK [debug] ******************************************************************************************************************************************************************************************************************************************************************************************************************************
ok: [emirr01] =>
msg: 'setup=[{''emirr01'': {''label'': ''label1''}}, {''emirr02'': {''label'': ''label2''}}, {''emirr03'': {''label'': ''label3''}}]'
TASK [debug] ******************************************************************************************************************************************************************************************************************************************************************************************************************************
ok: [emirr01] =>
msg: lookup=['emirr01', 'emirr02']
TASK [debug] ******************************************************************************************************************************************************************************************************************************************************************************************************************************
ok: [emirr01] => (item=[{'emirr01': {'label': 'label1'}}, 'emirr01']) =>
msg: 'item0={''emirr01'': {''label'': ''label1''}} item1=emirr01'
skipping: [emirr01] => (item=[{'emirr01': {'label': 'label1'}}, 'emirr02'])
ok: [emirr01] => (item=[{'emirr02': {'label': 'label2'}}, 'emirr01']) =>
msg: 'item0={''emirr02'': {''label'': ''label2''}} item1=emirr01'
skipping: [emirr01] => (item=[{'emirr02': {'label': 'label2'}}, 'emirr02'])
ok: [emirr01] => (item=[{'emirr03': {'label': 'label3'}}, 'emirr01']) =>
msg: 'item0={''emirr03'': {''label'': ''label3''}} item1=emirr01'
skipping: [emirr01] => (item=[{'emirr03': {'label': 'label3'}}, 'emirr02'])
TASK [set_fact] ***************************************************************************************************************************************************************************************************************************************************************************************************************************
fatal: [emirr01]: FAILED! =>
msg: |-
The conditional check 'item.0 == item.1.label' failed. The error was: error while evaluating conditional (item.0 == item.1.label): 'ansible.utils.unsafe_proxy.AnsibleUnsafeText object' has no attribute 'label'
The error appears to be in '/home/ansible/test.yml': line 21, column 7, but may
be elsewhere in the file depending on the exact syntax problem.
The offending line appears to be:
- set_fact:
^ here
It all boils down to "digging" for a value from a dictionary in a list. Does anyone have an idea of how to reach item.1.label? It's a little bit frustrating since this is trivial in Python or other programming languages but in Ansible... Or maybe my mistake is to use a wrong kind of with_* loop for this purpose?
Q: "Expect a list like this [label1, label2]"
setup:
- emirr01: { label: "label1" }
- emirr02: { label: "label2" }
- emirr03: { label: "label3" }
lookup: [ "emirr01", "emirr02"]
A: This is a typical example of a wrong data structure for this use-case. A list of dictionaries can't be used in mapping the filter extract. Moreover, the keys in the dictionaries don't have to be unique, because they are hidden in the items of the list. The solution is simple with the data in the dictionary
setup:
emirr01: { label: "label1" }
emirr02: { label: "label2" }
emirr03: { label: "label3" }
- set_fact:
use_labels: "{{ lookup|map('extract', setup, 'label')|list }}"
gives
use_labels:
- label1
- label2
One of the options is creating a dictionary from the list first. For example
- set_fact:
setup2: "{{ setup2|default({})|combine(item) }}"
loop: "{{ setup }}"
Then the dictionary setup2 can be used to get the same result
It's possible to filter the list directly. For example, the task below gives the same result too
- set_fact:
use_labels: "{{ setup|map('dict2items')|map('first')|
selectattr('key', 'in', lookup )|
map(attribute='value.label')|
list }}"
Your issue with the with_nested is actually coming from how those "two-lists" loops are working.
When you are using loops like with_nested, the item.0 is the item of the first list, while the item.1 is the item of the second list.
Because lookup is a list and not a list of dictionaries like
lookup:
- label: "emirr01"
- label: "emirr02"
You can use the value of item.1 right away.
So the way to have you issue fixed is to use it this way:
- set_fact:
use_labels: "{{ use_labels + [ item.0[item.1].label ] }}"
when: item.0[item.1].label is defined
with_nested:
- "{{ setup }}"
- "{{ lookup }}"
Here is a playbook demonstrating this:
- hosts: localhost
gather_facts: no
tasks:
- set_fact:
use_labels: "{{ use_labels | default([]) + [ item.0[item.1].label ] }}"
when: item.0[item.1].label is defined
with_nested:
- "{{ setup }}"
- "{{ lookup }}"
vars:
setup:
- emirr01:
label: "label1"
- emirr02:
label: "label2"
- emirr03:
label: "label3"
lookup:
- "emirr01"
- "emirr02"
- debug:
msg: "{{ use_labels }}"
Wich gives:
PLAY [localhost] ***************************************************************************************************
TASK [set_fact] ****************************************************************************************************
ok: [localhost] => (item=[{'emirr01': {'label': 'label1'}}, 'emirr01'])
skipping: [localhost] => (item=[{'emirr01': {'label': 'label1'}}, 'emirr02'])
skipping: [localhost] => (item=[{'emirr02': {'label': 'label2'}}, 'emirr01'])
ok: [localhost] => (item=[{'emirr02': {'label': 'label2'}}, 'emirr02'])
skipping: [localhost] => (item=[{'emirr03': {'label': 'label3'}}, 'emirr01'])
skipping: [localhost] => (item=[{'emirr03': {'label': 'label3'}}, 'emirr02'])
TASK [debug] *******************************************************************************************************
ok: [localhost] => {
"msg": [
"label1",
"label2"
]
}
PLAY RECAP *********************************************************************************************************
localhost : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
I am trying to write a task that stores the dictionary values in variable based on the condition .
I'm new to this technology. Please anyone help on the below request.
I tried with the below code. Please check below.
- set_fact:
v1: "{{ v1|default([]) + item.keys() if item.values() == false else 1 }}"
loop: "{{ dv }}"
'dv' is a dictionary.
[{1A:True},{2A:True},{3A:False},{4A:False}]
Actually, here I'm trying to store false values in v1 by using comparison operators only.
Expected output:
v1 should contain following list:
[3A,4A]
Ansible Version: 2.5.15
Below works for me:
---
- hosts: localhost
vars:
dv:
1A: 'True'
2A: 'False'
3A: 'True'
4A: 'False'
tasks:
- name: debug
debug:
msg: "{{ item.value }}"
loop: "{{ dv | dict2items }}"
- set_fact:
v1: "{{ v1| default([]) + item.key if (item.value in 'False') else('') }}"
loop: "{{ dv | dict2items }}"
- debug:
var: v1
output -->
TASK [set_fact] *********************************************************************************************************
ok: [localhost] => (item={'key': u'1A', 'value': u'True'})
ok: [localhost] => (item={'key': u'3A', 'value': u'True'})
ok: [localhost] => (item={'key': u'2A', 'value': u'False'})
ok: [localhost] => (item={'key': u'4A', 'value': u'False'})
TASK [debug] ************************************************************************************************************
ok: [localhost] => {
"v1": "2A4A"
}
You can try the below code.
- hosts: localhost
connection: local
vars:
dv: [{1A:True},{2A:True},{3A:False},{4A:False}]
v2: []
v1: []
tasks:
- set_fact:
v1: "{{ v1|default([]) }} + [ {{ v1.append((item.keys()|first).split(':')[0]) if (item.keys()|first).split(':')[1] == 'False' else v2.append('1') }} ]"
with_items: "{{ dv }}"
- debug:
msg: "{{ v1 }}"
Here v2 is a variable declared to direct if the conditions are not met.
Output of the a above code is a below:
ok: [localhost] => {
"msg": [
"3A",
"4A"
]
}
I guess it is not possible to refer to item variable when using
Jinja2 'default' filter?
Like in this example playbook:
---
- hosts: localhost
become: no
gather_facts: no
vars:
users:
- foo:
name: "foo"
home: /home/foo
- bar:
name: "bar"
tasks:
- name: debug
debug:
msg: "{{ item.home | default('/home/{{ item.name }}') }}"
loop: "{{ users }}"
If tried I get output like:
$ ansible-playbook test.yml |grep item
ok: [localhost] => (item={u'home': u'/home/foo', u'foo': None, u'name': u'foo'}) => {
ok: [localhost] => (item={u'bar': None, u'name': u'bar'}) => {
"msg": "/home/{{ item.name }}"
Obviously I want "/home/bar" not "/home/{{ item.name }}".
Just use string concatenation in the expression, don't use nested handlebars...
"{{ item.home | default('/home/' + item.name) }}"
This adds the item.name variable to the static /home part.