Using with_nested and Ansible - ansible

Would anyone be able to recommend a way to take the contents of two register variables and pass them into one command? While also lining up the results of the outputs from each variable in a 1:1 fashion. (ie. VS1:rule1, VS2:rule2, and so on from the output shown below)
Here is what's stored in stdout_lines for 'Virtual_Check' and 'Rule_Check':
"Virtual_Check.stdout_lines": [
[
"ltm virtual VS1 ",
"ltm virtual VS2 ",
"ltm virtual VS3 ",
"ltm virtual VS",
"Rule_Check.stdout_lines": [
[
"myrule1",
" ",
"",
" myrule2",
" ",
"",
" myrule3",
" ",
"",
" myrule4",
" ",
"",
Now, I would like to pass the contents of the variables into one command as shown below. When I run this playbook the 'Virtual_Check' portion under 'with_nested' loops as expected, but the issue I'm running into is it won't loop properly for the 'Rule_Check' portion (I've left in the two methods I tried below)
So far I've tried using with_nested to accomplish this and it seems to no be looping over the second variable correctly.
- name: Update VS iRule
bigip_command:
commands:
- "modify ltm virtual {{ item.0 }} rules { {{ item.1 }} myrule10 }"
provider:
server: "{{ inventory_hostname }}"
password: "{{ remote_passwd }}"
user: "{{ remote_username }}"
validate_certs: no
delegate_to: localhost
with_nested:
- [ "{{ Virtual_Check['stdout'][0] | replace('ltm virtual', '') | replace('\n', '') }}"]
- [ "{{ Rule_Check['stdout'][0] | replace('\n', '') }}" ]
- [ "{{ Rule_Check['stdout_lines'][0] }}" ]
I would expect that the 'modify ltm virtual {{ item.0 }} rules { {{ item.1 }} myrule10 }' line would be processed with the content within the Virtual_Check & Rule_Check lists
For example:
modify ltm virtual VS1 rules { myrule1 myrule10 }
modify ltm virtual VS2 rules { myrule2 myrule10 }
modify ltm virtual VS3 rules { myrule3 myrule10 }
modify ltm virtual VS4 rules { myrule4 myrule10 }

The nested lookup does not accomplish what your are expecting: it creates a loop on first element with a sub-loop on second element and a sub-sub-loop on third element, etc...
What you are looking for is the zip filter which will allow you to assemble several lists in a single one joining all items of same index together in a list.
Example below with your original sample data in you question. You just have to adapt to your real case:
---
- name: zip example
hosts: localhost
gather_facts: false
vars:
servers: [ 'VS1', 'VS2', 'VS3', 'VS4' ]
rules: [ myrule1, myrule2, myrule3, myrule4 ]
tasks:
- name: Show zipped data from servers and rules
debug:
msg: "Server {{ item.0 }} has rule: {{ item.1 }}"
loop: "{{ servers | zip(rules) | list }}"
which gives
PLAY [zip example] ********************************************************************************************************************************************************************************************************
TASK [Show zipped data from servers and rules] ****************************************************************************************************************************************************************************
ok: [localhost] => (item=['VS1', 'myrule1']) => {
"msg": "Server VS1 has rule: myrule1"
}
ok: [localhost] => (item=['VS2', 'myrule2']) => {
"msg": "Server VS2 has rule: myrule2"
}
ok: [localhost] => (item=['VS3', 'myrule3']) => {
"msg": "Server VS3 has rule: myrule3"
}
ok: [localhost] => (item=['VS4', 'myrule4']) => {
"msg": "Server VS4 has rule: myrule4"
}
PLAY RECAP ****************************************************************************************************************************************************************************************************************
localhost : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0

Related

Ansible - List of dictionaries

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

Why is ansible creating a new host scoped list variables for each host?

I need to create a variable of ip addresses that are derived from the run state of all hosts. I expect to insert a new string value and have it appended to the list.
When I run the following ansible-playbook, it creates what looks to be a new list instance for each host instead of modifying the playbook level vars.
My understanding is the set_fact below should concatenate the lists and assign them back to the play scoped var ip_list_from_terraform. Why am I getting host scoped results?
---
- name: Bootstrap nodes
hosts: all
become: true
gather_facts: true
vars:
ip_list_from_terraform: ['Verify']
tasks:
- name: assign a list of all the physical network private IPs
set_fact:
ip_list_from_terraform: "{{ ip_list_from_terraform + [ item ] }}"
with_items: " {{ hostvars[inventory_hostname]['ansible_' + ansible_default_ipv4['interface']]['ipv4']['address'] }} "
register: ip_list_from_terraform_list
- name: Debug global var
debug:
var: ip_list_from_terraform
- name: Debug register result
debug:
var: ip_list_from_terraform_list
Expected:
ok: [shrimp-master-0] => {
"ip_list_from_terraform": [
"Verify",
"10.0.2.41",
"10.0.2.172",
"10.0.2.33",
"10.0.2.215",
"10.0.2.131",
"10.0.2.168",
"10.0.2.118"
]
}
Actual:
TASK [Debug global var] ********************************************************************************************************************************************************************************************************************
ok: [shrimp-master-0] => {
"ip_list_from_terraform": [
"Verify",
"10.0.2.12"
]
}
ok: [shrimp-master-1] => {
"ip_list_from_terraform": [
"Verify",
"10.0.2.33"
]
}
ok: [shrimp-master-2] => {
"ip_list_from_terraform": [
"Verify",
"10.0.2.215"
]
}
ok: [shrimp-worker-0-super-wallaby] => {
"ip_list_from_terraform": [
"Verify",
"10.0.2.131"
]
}
ok: [shrimp-gpu-worker-0-settled-wombat] => {
"ip_list_from_terraform": [
"Verify",
"10.0.2.151"
]
}
Let's simplify the case. Given the inventory
shell> cat hosts
host1 test_ip=10.0.2.41
host2 test_ip=10.0.2.172
host3 test_ip=10.0.2.33
the playbook
- hosts: host1,host2,host3
vars:
ip_list_from_terraform: ['Verify']
tasks:
- set_fact:
ip_list_from_terraform: "{{ ip_list_from_terraform + [ item ] }}"
with_items: "{{ hostvars[inventory_hostname]['test_ip'] }}"
- debug:
var: ip_list_from_terraform
gives
ok: [host2] =>
ip_list_from_terraform:
- Verify
- 10.0.2.172
ok: [host1] =>
ip_list_from_terraform:
- Verify
- 10.0.2.41
ok: [host3] =>
ip_list_from_terraform:
- Verify
- 10.0.2.33
As a side-note, with_items is needed because the argument is a string. loop would crash with the error 'Invalid data passed to ''loop'', it requires a list,.
Q: "set_fact should concatenate the lists"
A: Run once and loop ansible_play_hosts. For example
- set_fact:
ip_list_from_terraform: "{{ ip_list_from_terraform +
[hostvars[item]['test_ip']] }}"
loop: "{{ ansible_play_hosts }}"
run_once: true
gives
ip_list_from_terraform:
- Verify
- 10.0.2.41
- 10.0.2.172
- 10.0.2.33

Ansible jinja test list if contains valid IP address and generate template

I have the following variables in Ansible:
my_allowed_hosts2:
- {host: patch, address: '202.167.24.50'}
- {host:localhost, address: '::1/128', extra_field: trust}
my_ferm_groups:
- firewall_whitelist1
- firewall_whitelist2
I have a task that checks if there are valid IP addresses in my_allowed_hosts2 and if so, generate a template for it (in this case, the host I'm running the playbook against is in firewall_whitelist2 group in my inventory). This template is then used to generate ferm/iptable rules to allow the IP addresses in my_allowed_hosts2 to access the host.
However, if there is no valid address in my_allowed_hosts2, I don't want to generate a template for it (firewall_whitelist_2) even though myhost is a valid member of that group - because if it has no valid IP addresses that the template will become invalid. Hope I'm making sense so far.
The relevant tasks :
- set_fact:
valid_addresses: "{{ my_allowed_hosts2|json_query('[*].address')| map('ipaddr')|reject('match','^127.0.0.1|::1/128') | list }}"
- debug:
msg: "{{ valid_addresses }}"
- name: Loop dictionary and copy group configs in place
template: src=etc/ferm/ferm.d/{{ item }}.j2 dest=/etc/ferm/ferm.d/{{ item }}.conf owner=root group=root mode=0640
when: item in my_ferm_groups
with_items: "{{ group_names | reject ('match','^firewall_whitelist2$') if valid_addresses is not any }}"
What I was trying to do above is trying to remove firewall_whitelist2 from the loop if there is no true value tested in my_allowed_hosts2 list - thus preventing it to create that particular template.
However, I receive the following error :
fatal: [myhost]: FAILED! => {
"msg": "the inline if-expression on line 1 evaluated to false and no else section was defined."
}
What I am expecting (IF valid_addresses is not empty) is something like the below. The template gets copied accordingly:
ok: [myhost] => (item=firewall_whitelist1) => {"ansible_loop_var": "item", "changed": false, "checksum": "21f42490d91da92c6f404a30df6e34373266b72f", "dest": "/etc/ferm/ferm.d/firewall_whitelist1.j2", "gid": 0, "group": "root", "item": "firewall_whitelist1", "mode": "0640", "owner": "root", "path": "/etc/ferm/ferm.d/firewall_whitelist1.conf", "secontext": "system_u:object_r:etc_t:s0", "size": 570, "state": "file", "uid": 0}
changed: [myhost] => (item=firewall_whitelist2) => {"ansible_loop_var": "item", "changed": false, "checksum": "985d207faa196b285c20c7f60f6aa69b23f908b9", "dest": "/etc/ferm/ferm.d/firewall_whitelist2.j2, "gid": 0, "group": "root", "item": "firewall_whitelist2", "mode": "0640", "owner": "root", "path": "/etc/ferm/ferm.d/firewall_whitelist2.conf", "secontext": "system_u:object_r:etc_t:s0", "size": 705, "state": "file", "uid": 0}
I tried this too in an effort to add firewall_whitelist2 to the list so the template only gets generated when valid_addresses is not empty:
- name: Loop dictionary and copy group configs in place
template: src=etc/ferm/ferm.d/{{ item }}.j2 dest=/etc/ferm/ferm.d/{{ item }}.conf owner=root group=root mode=0640
when: item in my_ferm_groups
loop: "{{ group_names + 'firewall_whitelist2' if valid_addresses|length > 0 else group_names }}"
But this errors out with
FAILED! => {"msg": "Unexpected templating type error occurred on ({{ group_names + 'firewall_whitelist2' if valid_addresses|length > 0 else group_names }}): can only concatenate list (not \"str\") to list"}`
Any idea how can I make this work?
Basically I'm trying to find a way to only generate that template for firewall_whitelist_2 ONLY IF the condition matches (valid IP addresses detected in my_allowed_hosts2).
Any nudge in the right direction would help.
The tasks below
- set_fact:
valid_addresses: "{{ my_allowed_hosts2|
json_query('[].address')|
map('ipaddr')|
difference(['127.0.0.1', '::1/128']) }}"
- debug:
var: valid_addresses
gives
"valid_addresses": [
"202.167.24.50"
]
Is this probably the loop you're looking for?
- debug:
var: item
loop: "{{ group_names|difference(['firewall_whitelist2']) }}"
when:
- item in my_ferm_groups
- valid_addresses|length > 0
Makes me feel like you are trying to fit in the loop what should be in the when.
So in the ultimate end you will probably loop on an empty array, when you could as well loop on a populated list and skip all items.
When moving all those conditions in the when, we could end up with:
- template:
src: /etc/ferm/ferm.d/{{ item }}.j2
dest: /etc/ferm/ferm.d/{{ item }}.conf
owner: root
group: root
mode: 0640
when:
- item in my_ferm_groups
- item != 'firewall_whitelist2' or valid_addresses|length > 0
loop: "{{ group_names }}"
Here is are two examples of this:
When there is no matching IP
- hosts: all
gather_facts: no
tasks:
- debug:
msg: "{{ item }}"
when:
- item in my_ferm_groups
- item != 'firewall_whitelist2' or valid_addresses|length > 0
loop: "{{ group_names }}"
vars:
valid_addresses: []
group_names:
- firewall_whitelist1
- firewall_whitelist2
- firewall_whitelist3
- firewall_whitelist4
my_ferm_groups:
- firewall_whitelist1
- firewall_whitelist2
- firewall_whitelist3
This gives the recap:
PLAY [all] *******************************************************************************************************
TASK [debug] *****************************************************************************************************
ok: [localhost] => (item=firewall_whitelist1) => {
"msg": "firewall_whitelist1"
}
skipping: [localhost] => (item=firewall_whitelist2)
ok: [localhost] => (item=firewall_whitelist3) => {
"msg": "firewall_whitelist3"
}
skipping: [localhost] => (item=firewall_whitelist4)
PLAY RECAP *******************************************************************************************************
localhost : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
When there is any matching IP
- hosts: all
gather_facts: no
tasks:
- debug:
msg: "{{ item }}"
when:
- item in my_ferm_groups
- item != 'firewall_whitelist2' or valid_addresses|length > 0
loop: "{{ group_names }}"
vars:
valid_addresses:
- 10.1.1.1
group_names:
- firewall_whitelist1
- firewall_whitelist2
- firewall_whitelist3
- firewall_whitelist4
my_ferm_groups:
- firewall_whitelist1
- firewall_whitelist2
- firewall_whitelist3
This gives the recap:
PLAY [all] *******************************************************************************************************
TASK [debug] *****************************************************************************************************
ok: [localhost] => (item=firewall_whitelist1) => {
"msg": "firewall_whitelist1"
}
ok: [localhost] => (item=firewall_whitelist2) => {
"msg": "firewall_whitelist2"
}
ok: [localhost] => (item=firewall_whitelist3) => {
"msg": "firewall_whitelist3"
}
skipping: [localhost] => (item=firewall_whitelist4)
PLAY RECAP *******************************************************************************************************
localhost : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
With all this, the valid_addresses you are populating stays as you have it right now.

Ansible template with list of hosts excluding current

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"
]
}

Run task only if host does not belong to a mare than one group

Run task only if host does not belong more than one group:
eg:
[web_1]
[one_web_2]
[server_3]
I whant to mach on first two groups:
"'web' not in group_names" ->> first and second
?
There is the Special Variable group_names
group_names List of groups the current host is part of
The play below shows how many from the selected groups (my_groups) the host is a member of.
- hosts: all
gather_facts: no
vars:
my_groups: [ 'web_1', 'web_2', 'web_3' ]
tasks:
- debug:
msg: "{{ inventory_hostname }} is member of
{{ my_groups|intersect(group_names)|length }} group(s)."
Let's have the inventory below
[test]
test_01
test_02
test_03
[web_1]
test_01
test_02
[web_2]
test_01
test_02
[web_3]
test_03
The play gives
ok: [test_01] => {
"msg": "test_01 is member of 2 group(s)."
}
ok: [test_02] => {
"msg": "test_02 is member of 2 group(s)."
}
ok: [test_03] => {
"msg": "test_03 is member of 1 group(s)."
}
Use the play below to
Run task only if host does not belong more than one group
- hosts: all
gather_facts: no
vars:
my_groups: [ 'web_1', 'web_2', 'web_3' ]
tasks:
- fail:
msg: "{{ inventory_hostname }} is member of
{{ my_groups|intersect(group_names)|length }} groups.
Play failed."
when: my_groups|intersect(group_names)|length != 1
- debug:
msg: "{{ inventory_hostname }} is member of
{{ my_groups|intersect(group_names)|length }} group.
Play continues."

Resources