This has got to be a simple question but I can't seem to find the answer anywhere.
I have the following list:
yum_repo_ip_addrs: ['172.16.130.4', '172.16.130.1']
I want to dynamically create a list called yum_baseurls where I copy in these values into the list along with the rest of the url. The list should ultimately look like this when run successfully:
yum_baseurls:
- "http://172.16.130.4/repos/elrepo-8-x86_64"
- "http://172.16.130.1/repos/elrepo-8-x86_64"
Instead, I'm finding that after the first iteration of my loop it's pasting in the variables literally.
Here's my playbook:
---
- name: Print the list of baseurl IP addresses.
debug:
msg: "{{ yum_repo_ip_addrs }}"
- name: Create the list of baseurls.
set_fact:
yum_baseurls: "{{ yum_baseurls + ['http://{{ item }}/repos/elrepo-{{ ansible_distribution_major_version }}-{{ ansible_userspace_architecture }}'] }}"
with_items:
- "{{ yum_repo_ip_addrs }}"
- name: print the list of baseurls.
debug:
msg: "{{ yum_baseurls }}"
And here's the output I get when I run it:
TASK [yum : Print the list of baseurl IP addresses.] ***************************************************************************************
ok: [ansibletarget3.jnk.sys] => {
"msg": [
"172.16.130.4",
"172.16.130.1"
]
}
TASK [yum : Create the list of baseurls.] **************************************************************************************************
ok: [ansibletarget3.jnk.sys] => (item=172.16.130.4)
ok: [ansibletarget3.jnk.sys] => (item=172.16.130.1)
TASK [yum : print the list of baseurls.] ***************************************************************************************************
ok: [ansibletarget3.jnk.sys] => {
"msg": [
"http://172.16.130.1/repos/elrepo-8-x86_64",
"http://{{ item }}/repos/elrepo-{{ ansible_distribution_major_version }}-{{ ansible_userspace_architecture }}"
]
}
Is there a better way to generate my list?
I'd remove it from the code and put it somewhere into the vars, e.g.
yum_repo_ip_addrs: [172.16.130.4, 172.16.130.1]
version: 8
architecture: x86_64
yum_baseurls_str: |
{% for ip in yum_repo_ip_addrs %}
- http://{{ ip }}/repos/elrepo-{{ version }}-{{ architecture }}
{% endfor %}
yum_baseurls: "{{ yum_baseurls_str|from_yaml }}"
Related
Let me introduce my problem. I have some list of dictionary in my Ansible code:
my_example_list = [
{
"key1" : "value_of_first_key"
},
{
"key2": "value_of_second_key"
},
{
"key3": "value_of_third_key"
}
]
I need execute command which will iterate over this list and it should look something like:
- name: 'Example'
shell: 'Here is my {{ item.key }} and here is {{ item.value }}'
What I've do or try to do:
I was trying to do that with with_items but i'm not able to point into value of particular key.
I've also try to filter values using | first and | last but it's not worked in my case.
What I want to achieve:
Creating loop which will iterate via that list and inject separated key and value into command.
I was asked to show how I was trying to resolve my issue:
Here is some code:
# Showing last component failing
- name: "Try to show last component of my list"
debug:
msg: "{{ my_example_list[1] | last }}"
# When i'm trying to show first component of my list i get "key1"
- name: "Try to show first component of my list"
debug:
msg: "{{ my_example_list[1] | first }}"
# This shows me my list of dict
- name: "Trying use with_items"
debug:
msg: "{{ item }}"
with_items: "{{ my_example_list }}"
# But when i'm trying point to key and value for example
- name: "Trying use with_items point to key and value"
debug:
msg: "Here is my {{ item.key }} which keep {{ item.value }}"
with_items: "{{ my_example_list }}"
# It's failing.
Sorry it's not maybe solution with using loop. I'm just stack with that issue over few days... And as first step I want to know how correctly point to pair keys and values.
It also works well:
- name: Correct solution
debug:
msg: "This is my {{ item.key }} and my value {{ item.value }}"
with_dict: "{{ my_example_list }}"
Thanks #U880D for help! I'm not able to add some plus for your solution because I'm new joiner. Appreciate your answer! :)
Your data structure and naming seems to be unfavorable. There is no need to number the key name and therefore it should be avoided. Furthermore counting list elements in Python starts at 0 not 1.
The following minimal example playbook
---
- hosts: localhost
become: false
gather_facts: false
vars:
example_list: |
[
{
"key1" : "value_of_first_key"
},
{
"key2": "value_of_second_key"
},
{
"key3": "value_of_third_key"
}
]
tasks:
- name: Example loop
debug:
msg: "{{ item }} is of type {{ item | type_debug }}"
loop: "{{ example_list }}"
- name: Example loop
debug:
msg: "{{ item.values() }}"
loop: "{{ example_list }}"
will result into an output of
TASK [Example loop] ******************************************
ok: [localhost] => (item={u'key1': u'value_of_first_key'}) =>
msg: '{u''key1'': u''value_of_first_key''} is of type dict'
ok: [localhost] => (item={u'key2': u'value_of_second_key'}) =>
msg: '{u''key2'': u''value_of_second_key''} is of type dict'
ok: [localhost] => (item={u'key3': u'value_of_third_key'}) =>
msg: '{u''key3'': u''value_of_third_key''} is of type dict'
TASK [Example loop] ******************************************
ok: [localhost] => (item={u'key1': u'value_of_first_key'}) =>
msg:
- value_of_first_key
ok: [localhost] => (item={u'key2': u'value_of_second_key'}) =>
msg:
- value_of_second_key
ok: [localhost] => (item={u'key3': u'value_of_third_key'}) =>
msg:
- value_of_third_key
Further Readings
How to work with lists and dictionaries in Ansible
Extended loop variables
Trying to create a dictionary per item and merge them
---
- name: TestingLab
hosts: localhost
gather_facts: False
tasks:
- name: Hello Vars
set_fact:
two_nums:
- 1
- 2
- name: create empty dict
set_fact:
ids: {}
- name: Merge all
vars:
single_entry: "{ '{{ item }}': {{ item }} }"
set_fact:
ids: "{{ ids | combine(single_entry) }}"
with_items: "{{ two_nums }}"
- name: Print Result
debug:
msg: "{{ ids }}"
I thought I followed the right guidelines but I seem to be getting only the last item afterwards:
ok: [localhost] => {
"msg": {
"2": 2
} }
I tried replacing the single_entry with the expression in vars but it does not run.
Is there a different syntax to get this done?
EDIT: version info
ansible-playbook 2.5.1
python version = 2.7.17 [GCC 7.5.0]
Try the filters dict and zip. The zip is available since 2.3, e.g.
- set_fact:
d2: "{{ dict(two_nums|zip(two_nums)) }}"
- debug:
var: d2
- debug:
var: d2|type_debug
gives
d2:
1: 1
2: 2
d2|type_debug: dict
If this does not work try Jinja and the filter from_yaml, e.g.
- hosts: localhost
vars:
two_nums:
- 1
- 2
l1: |-
{% for i in two_nums %}
{{ i }}: {{ i }}
{% endfor %}
tasks:
- set_fact:
d1: "{{ l1|from_yaml }}"
- debug:
var: d1
- debug:
var: d1|type_debug
gives the same result
d1:
1: 1
2: 2
d1|type_debug: dict
If you need the keys to be strings quote it, e.g.
l1: |-
{% for i in two_nums %}
"{{ i }}": {{ i }}
{% endfor %}
gives
d1:
'1': 1
'2': 2
In the first case, map the list's items to string, e.g.
- set_fact:
d2: "{{ dict(two_nums|map('string')|zip(two_nums)) }}"
gives the same result
d2:
'1': 1
'2': 2
I can't reproduce the behavior you're describing. Running your
playbook verbatim, I get as output:
TASK [Print Result] **************************************************************************************************************************************************************************
ok: [localhost] => {
"msg": {
"1": 1,
"2": 2
}
}
I'm using Ansible core 2.11.2, but I've also tested your playbook with Ansible 2.9.20 and I get the same output.
I would probably drop the set_fact task, and also change how you're
setting single_entry:
- name: TestingLab
hosts: localhost
gather_facts: False
tasks:
- name: Hello Vars
set_fact:
two_nums:
- 1
- 2
- name: Merge all
vars:
ids: {}
single_entry: "{{ {item: item} }}"
set_fact:
ids: "{{ ids | combine(single_entry) }}"
with_items: "{{ two_nums }}"
- name: Print Result
debug:
msg: "{{ ids }}"
In this version, the template expression is returning a dictionary,
and only requires a single set of Jinja template markers. I'm curious
if this version behaves any differently for you.
I am trying to run a nested for loop in order to retrieve a nested value.
I would like to retrieve some_value_4 when some_value_3 matches a criteria that's predefined.
{
"some_dict": {
"some_key_0": "value_0",
"some_key_1": "value_1"
},
"testval_dict": {
"test_key_0": "some_value_0",
"test_key_1": "some_value_1",
"test_key_2": "some_value_2",
"test_key_3": "some_value_3",
"test_key_4": "some_value_4"
}
}
The playbook:
- hosts: localhost
tasks:
- set_fact:
mydict: "{{ lookup('file', '/tmp/file.json' ) | from_json }}"
- debug:
msg: |
"{% for item in mydict %}
{{ item }}
{% endfor %}"
when run, it alreay looks like dict names are treated as string and nothing more:
ansible-playbook /tmp/test_playbook.yml -c local -i ', localhost'
TASK [debug] ******************************************************************
ok: [localhost] => {}
MSG:
" somee_dict
testval_dict
"
Then when I add an itme.key to the debug task, the playbook fails:
MSG:
The task includes an option with an undefined variable. The error was: 'str object' has no attribute 'value'
Thank you.
edit for clarification
In the real example, I will not know the names of the dicts, so I cannot use some_dict or testval_dict, that is why I'm trying to go over this data source in an item.key or item.value form.
Q: "{% for item in mydict %} ... dict names are treated as string and nothing more."
A: This is correct. A dictionary, when evaluated as a list, returns the list of its keys. See some examples below
- debug:
msg: "{{ mydict.keys()|list }}"
- debug:
msg: "{{ mydict[item] }}"
loop: "{{ mydict.keys()|list }}"
- debug:
msg: "{{ mydict|difference(['testval_dict']) }}"
give
msg:
- some_dict
- testval_dict
msg:
some_key_0: value_0
some_key_1: value_1
msg:
test_key_0: some_value_0
test_key_1: some_value_1
test_key_2: some_value_2
test_key_3: some_value_3
test_key_4: some_value_4
msg:
- some_dict
See How to iterate through a list of dictionaries in Jinja template?
If you need to loop over the dictionary, you can use with_dict loop functionality. This way, if you loop over mydict and get item.key you will get somee_dict and testval_dict.
tasks:
- set_fact:
mydict: "{{ lookup('file', '/tmp/file.json')|from_json }}"
# This will get the top level dictionaries somee_dict and testval_dict
- debug:
var: item.key
with_dict: "{{ mydict }}"
And if you get item.value of mydict you will get the sub-dictionary:
- debug:
var: item.value
with_dict: "{{ mydict }}"
Will produce (showing output only for testval_dict):
"item.value": {
"test_key_0": "some_value_0",
"test_key_1": "some_value_1",
"test_key_2": "some_value_2",
"test_key_3": "some_value_3",
"test_key_4": "some_value_4"
}
I'm super fresh to ansible and creating a playbook that in one of the tasks should copy templated file and replace values in 2 lines. First line should have current hostname, and in second semicolon separated list of all other hosts (used in the play) - it will be different group
First line is super easy, as it's just:
localnode={{ inventory_hostname }}
but I'm having problem with exclusion in the second line. I'd like something similar to:
{% for host in groups.nodes -%} # but without inventory_hostname
othernodes={{ host }}{% if not loop.last %};{% endif %}
{%- endfor %}
Given the inventory of:
nodes:
hosts:
hosta:
hostb:
hostc:
hostd:
I'd like to get following output (example for hostd):
localnode=hostd
othernodes=hosta,hostb,hostc
I'll be very grateful for all hints on possible solution
Create the list of hosts without inventory_hostname and use it in the template
- set_fact:
list_other_hosts: "{{ groups.nodes|difference([inventory_hostname]) }}"
Simplify the template
othernodes={{ list_other_hosts|join(';') }}
As an example, the inventory
shell> cat hosts
test_jails:
hosts:
test_01:
test_02:
test_03:
and the play
- hosts: test_jails
tasks:
- set_fact:
list_other_hosts: "{{ groups.test_jails|
difference([inventory_hostname]) }}"
- debug:
msg: "{{ msg.split('\n') }}"
vars:
msg: |-
localnode={{ inventory_hostname }}
othernodes={{ list_other_hosts|join(';') }}
give
TASK [debug] ********************************************************
ok: [test_01] => {
"msg": [
"localnode=test_01",
"othernodes=test_02;test_03"
]
}
ok: [test_02] => {
"msg": [
"localnode=test_02",
"othernodes=test_01;test_03"
]
}
ok: [test_03] => {
"msg": [
"localnode=test_03",
"othernodes=test_01;test_02"
]
}
I am trying to create a simple playbook task traversing over multiple dict items(v1) and create a new list var (list_var) with specific item in it, but it is not working list_var is only showing me one element, can anyone please suggest what am i missing?
if i do this:
set_fact:
list_var: "{{ v1.stdout }}"
with_items: "{{ v1.items }}"
Values in v1 are returned by simple shell output and have values from different hosts like hostname
Probably something list this?
- command: echo "{{ item }}"
register: v1
with_sequence: start=0 end=3
- set_fact:
list_var: "{{ list_var|default([]) }} + [ {{ item }} ]"
loop: "{{ v1.results | json_query('[].stdout') }}"
- debug: var=list_var
Which produces this list
TASK [debug] **********
ok: [localhost] => {
"list_var": [
0,
1,
2,
3
]
}