How to loop over a dictionary in Ansible - ansible

I have a following dictionary in default/main.yml
servers_as:
ABxxxxx1:
- server1-WASN_xcv_02.service
- server2-WASN_xcv_01.service
- server2-WASN_xcv_02.service
CBxxxx2:
- server1-abcd_02.service
- server1-abcd_03.service
- server2-abcd_01.service
- server2-abcd_02.service
- server2-abcd_03.service
I have tried to output each value with length, but it just gave me the last value.
How can i run it from 0-(servers_as | legnth )
- name: check output list
debug:
msg: "{{ item.value[ lookup('dict', servers_as) | length] }}"
loop: "{{ lookup('dict', servers_as) }}"
when:
- "item.key in inventory_hostname"
Expected output is the name of each value in order to start services:
- name: Start all services
systemd:
name: "{{ item.value }}.service"
state: started
loop: "{{ lookup('dict', servers_as) }}"
when:
- "item.key in inventory_hostname"

As a hint on how to iterate the lists
- name: Start all services
debug:
msg: "Start {{ item }}"
loop: "{{ servers_as[inventory_hostname]|d([]) }}"
gives
ok: [ABxxxx1] => (item=server1-WASN_xcv_02.service) =>
msg: Start server1-WASN_xcv_02.service
ok: [ABxxxx1] => (item=server2-WASN_xcv_01.service) =>
msg: Start server2-WASN_xcv_01.service
ok: [ABxxxx1] => (item=server2-WASN_xcv_02.service) =>
msg: Start server2-WASN_xcv_02.service
ok: [CBxxxx2] => (item=server1-abcd_02.service) =>
msg: Start server1-abcd_02.service
ok: [CBxxxx2] => (item=server1-abcd_03.service) =>
msg: Start server1-abcd_03.service
ok: [CBxxxx2] => (item=server2-abcd_01.service) =>
msg: Start server2-abcd_01.service
ok: [CBxxxx2] => (item=server2-abcd_02.service) =>
msg: Start server2-abcd_02.service
ok: [CBxxxx2] => (item=server2-abcd_03.service) =>
msg: Start server2-abcd_03.service
If this is what you want, use systemd instead of debug
- name: Start all services
systemd:
name: "{{ item }}"
state: started
loop: "{{ servers_as[inventory_hostname]|d([]) }}"

Related

Multiple 'and' operators in Ansible 'when' condition

This is the code that I am trying to run
- name: Read and Register Contents of .bash_profile
shell: grep -E 'AB_AG_HOME|AB_AG_LOCAL_ROOT|AB_AG_LOCAL_DIR|AB_AG_CONFIG_DIR|AB_AG_LOG_DIR' /home/username/.bash_profile
register: output
- debug:
msg: "{{ output.stdout_lines }}"
- name: Append AG environment variables in .bash_profile
shell: cat /home/{{ admin_user }}/tmp_bash.profile >> /home/{{ admin_user }}/.bash_profile
when: "'AB_AG_HOME' and 'AB_AG_LOCAL_ROOT' and 'AB_AG_LOCAL_DIR' and 'AB_AG_CONFIG_DIR' and 'AB_AG_LOG_DIR' not in item"
with_items: "{{ output.stdout_lines }}"
- name: Delete the temporary tmp_bash.profile"
file:
path: /home/{{ abinitio_admin_user }}/tmp_bash.profile
state: absent
when I run this code, all the values are repeated 4 times.
Is there anything that is missing?
By looping over the stdout_lines via
with_items: "{{ output.stdout_lines }}"`
the condition
when: "'AB_AG_HOME' and 'AB_AG_LOCAL_ROOT' and 'AB_AG_LOCAL_DIR' and 'AB_AG_CONFIG_DIR' and 'AB_AG_LOG_DIR' not in item"
would only be true if all strings are in one line (item) together.
You may have a look into the following minimal example
---
- hosts: localhost
become: false
gather_facts: false
vars:
result:
stdout_lines:
- "A1"
- "A2"
- "A3"
- "A4"
- "A5"
tasks:
- name: Debug one-line conditional
debug:
msg: "Not in stdout_lines"
when: "'A1' and 'A2' and 'A3' and 'A4' and 'A5' not in result.stdout_lines"
Whereby the first given example get skipped because of the condition, the second example
- name: Debug loop conditional
debug:
msg: "Not in {{ result.stdout_lines[ansible_loop.index0] }}"
when: "'A1' and 'A2' and 'A3' and 'A4' and 'A5' not in item"
loop: "{{ result.stdout_lines }}"
loop_control:
extended: true
will result into an output of
TASK [Debug loop conditional] ******
ok: [localhost] => (item=A1) =>
msg: Not in A1
ok: [localhost] => (item=A2) =>
msg: Not in A2
ok: [localhost] => (item=A3) =>
msg: Not in A3
ok: [localhost] => (item=A4) =>
msg: Not in A4
For further debugging you could also use the assert module – Asserts given expressions are true.

Unicode error while increment variable value

When I work with static variables it works absolutely fine. But when I try to use dynamic it does not work.
The playbook:
---
- hosts: Swi1
vars:
NewOne: 0
provider:
host: "192.168.0.30"
transport: "cli"
username: "cisco"
password: "cisco"
tasks:
- name: gather facts
register: iosfacts
ios_facts:
provider: "{{ provider }}"
- name: Display the value of the counter
debug:
msg: "NewOne={{ NewOne }} / Data type={{ NewOne | type_debug }}"
- name: interface description
set_fact:
NewOne: " {{ NewOne + 1 }}"
parents: "interface {{ item.key }}"
with_dict: "{{ iosfacts.ansible_facts.ansible_net_interfaces }}"
when: item.value.operstatus == "up"
- debug:
msg: " This is Debug {{ NewOne }}"
Gives the error:
fatal: [Swi1]: FAILED! => {"msg": "Unexpected templating type error
occurred on ({{ NewOne + 1 }}): coercing to Unicode: need string or
buffer, int found"}
If you want to do an increment on a variable, you need to recast it as an int, as set_fact will always make you end up with a string.
As an example, the two tasks:
- set_fact:
NewOne: "{{ NewOne | d(0) + 1 }}"
- debug:
var: NewOne | type_debug
Are giving
TASK [set_fact] ***************************************************************
ok: [localhost]
TASK [debug] ******************************************************************
ok: [localhost] =>
NewOne | type_debug: str
The fix is, then, to use the int filter.
Given:
- set_fact:
NewOne: "{{ NewOne | d(0) | int + 1 }}"
loop: "{{ range(1, 4) }}"
- debug:
var: NewOne
This yields the expected
TASK [set_fact] ***************************************************************
ok: [localhost] => (item=1)
ok: [localhost] => (item=2)
ok: [localhost] => (item=3)
TASK [debug] ******************************************************************
ok: [localhost] =>
NewOne: '3'
But then with your use case, there are more elaborated and shorter way to achieve the same:
- set_fact:
NewOne: >-
{{
iosfacts
.ansible_facts
.ansible_net_interfaces
| selectattr('value.operstatus', '==', 'up')
| length
}}
Given:
- debug:
msg: >-
{{
iosfacts
.ansible_facts
.ansible_net_interfaces
| selectattr('value.operstatus', '==', 'up')
| length
}}
vars:
iosfacts:
ansible_facts:
ansible_net_interfaces:
- value:
operstatus: up
- value:
operstatus: down
- value:
operstatus: up
This yields:
ok: [localhost] =>
msg: '2'
It seems you are trying to implement a loop counter with a programming paradigm, which isn't plain possible in that way since Ansible is not a programming language but a Configuration Management Tool in which you declare a state.
Your current issue is reproducible in the following way:
---
- hosts: localhost
become: false
gather_facts: false
vars:
NewOne: 0
tasks:
- name: Show var
debug:
msg: "{{ NewOne | type_debug }}"
- name: Add value
set_fact:
NewOne: " {{ NewOne + 1 }}"
loop: [1, 2, 3]
- name: Show result
debug:
msg: "{{ NewOne }}
resulting into an output of
TASK [Add value] *************
ok: [localhost] => (item=1)
fatal: [localhost]: FAILED! =>
msg: 'Unexpected templating type error occurred on ( {{ NewOne + 1 }}): coercing to Unicode: need string or buffer, int found'
Possible Solutions
You may have a look into Migrating from with_X to loop and Extended loop variables as an iteration counter is already provided there.
An other approach is given via type casting with filter in the answer of #β.εηοιτ.βε.
There as well if you are just interested in the amount of occurrences of certain status, like interface status up or down.
Further Q&A
Ansible set_fact type cast
Further Documentation
Discovering the data type
Forcing the data type

ansible Iterate var only if var is not empty

I m trying to iterate over some variables in an ansible role. However, I want to ignore if the var is empty ex:ns3 from below code? I m trying when item length is greater than 0 but it seems not working? any ideas on how to do it?
---
- hosts: localhost
gather_facts: no
vars:
ns1: adm_analytics
ns2: adm_snap
ns3: ""
ns4: adm_eck
tasks:
- name: print namespace loop
with_items:
- "{{ ns1 }}"
- "{{ ns2 }}"
- "{{ ns3 }}"
- "{{ ns4 }}"
include_role:
name: verify_pod_status
vars:
NAMESPACE: "{{ item }}"
when: "{{ item | lenght > 0 }}"
You may have a look into the documentation about Conditionals.
There are some typos in your when clause.
---
- hosts: localhost
gather_facts: false
vars:
ns1: adm_analytics
ns2: adm_snap
ns3: ""
ns4: adm_eck
tasks:
- name: Print namespace loop
debug:
msg: "{{ item }}"
when: item | length > 0
with_items:
- "{{ ns1 }}"
- "{{ ns2 }}"
- "{{ ns3 }}"
- "{{ ns4 }}"
result into an output of
TASK [Print namespace loop] **************
ok: [localhost] => (item=adm_analytics) =>
msg: adm_analytics
ok: [localhost] => (item=adm_snap) =>
msg: adm_snap
ok: [localhost] => (item=adm_eck) =>
msg: adm_eck
when: condition is expanded by default. Fix the syntax
when: item|length > 0
Make your life easier and put the ns* variables into a dictionary. Then you can simply reference the values in the loop instead of listing the variables again. For example, the playbook
shell> cat playbook.yml
- hosts: localhost
gather_facts: no
vars:
ns:
ns1: adm_analytics
ns2: adm_snap
ns3: ""
ns4: adm_eck
tasks:
- name: print namespace loop
include_role:
name: verify_pod_status
loop: "{{ ns.values()|list }}"
vars:
NAMESPACE: "{{ item }}"
when: item|length > 0
and the role
shell> cat roles/verify_pod_status/tasks/main.yml
- debug:
var: NAMESPACE
give (abridged)
TASK [verify_pod_status : debug] ***********************************
skipping: [localhost] => (item=)
TASK [verify_pod_status : debug] ***********************************
ok: [localhost] =>
NAMESPACE: adm_analytics
TASK [verify_pod_status : debug] ***********************************
ok: [localhost] =>
NAMESPACE: adm_snap
TASK [verify_pod_status : debug] ***********************************
ok: [localhost] =>
NAMESPACE: adm_eck

Ansible combine arrays in dict

I've got dict with array like this
tests:
test01:
state: 'enabled'
objects:
- 'A111'
- 'B111'
test02:
state: 'enabled'
objects:
- 'C222'
- 'D222'
test03:
state: 'enabled'
objects:
- 'E333'
- 'F333'
How to combine array "objects" together in one output? The result should be
"msg": "A111,B111,E333,F333,C222,D222"
Is this what you need
- debug:
msg: "{{(tests.test01.objects,tests.test02.objects,tests.test03.objects)|flatten|join('\n')|replace('\n', ',')}}"
ok: [localhost] => {
"msg": "A111,B111,C222,D222,E333,F333"
}
Here is multiliner but without hardcoding of items
- set_fact:
tests_dict: "{{ item }}"
with_dict: "{{ tests }}"
register: tests_items
- set_fact:
tests_objects: "{{ tests_objects }} + {{ item.item.value.objects }}"
with_items: "{{ tests_items.results }}"
vars:
tests_objects: []
- debug:
msg: "{{ tests_objects | join(',') }}"
ok: [127.0.0.1] => {
"msg": "C222,D222,E333,F333,A111,B111" }

Variable value for another variable ansible

Sorry if there are many posts about variables inside variable my use case is different.
Trying to access an element from a variable list "efs_list" based on the index-number of the current host. There are three hosts in the inventory
vars:
efs_list:
- efs1
- efs2
- efs3
sdb_index: "{{ groups['all'].index(inventory_hostname) }}"
The values should be as follows
host1- efs1
host2- efs2
host3- efs3
Tried accessing it through efs_list.{{ sdb_index }}
for - debug: var=efs_list.{{ sdb_index }} the output is as intended
ok: [10.251.0.174] => {
"efs_list.0": "efs1"
}
ok: [10.251.0.207] => {
"efs_list.1": "efs2"
}
ok: [10.251.0.151] => {
"efs_list.2": "efs3"
}
But for
- debug:
msg: "{{ efs_list.{{ sdb_index }} }}"
fatal: [10.251.0.174]: FAILED! => {"msg": "template error while templating string: expected name or number. String: {{ efs_list.{{ sdb_index }} }}"}
---
- name: SDB Snapshots Creation
hosts: all
remote_user: "centos"
become: yes
vars:
efs_list:
- efs1
- efs2
- efs3
sdb_index: "{{ groups['all'].index(inventory_hostname) }}"
tasks:
- debug: var=efs_list.{{ sdb_index }}
- debug:
msg: "{{ efs_list.{{ sdb_index }} }}"
- name: Get Filesystem ID
become: false
local_action: command aws efs describe-file-systems --creation-token "{{ efs_list.{{ sdb_index }} }}"
--region us-east-1 --query FileSystems[*].FileSystemId --output text
register: fs_id
It should attribute the element of list to current indexenter code here
extract filter will do the job. The input of the filter must be a list of indices and a container (array in this case). The tasks below
- set_fact:
sdb_index: "{{ [] + [ groups['all'].index(inventory_hostname) ] }}"
- debug:
msg: "{{ sdb_index|map('extract', efs_list)|list }}"
give
ok: [host1] => {
"msg": [
"efs1"
]
}
ok: [host2] => {
"msg": [
"efs2"
]
}
ok: [host3] => {
"msg": [
"efs3"
]
}
If the hosts are not sorted in the inventory it's necessary to sort them in the play
- set_fact:
my_hosts: "{{ groups['all']|sort }}"
- set_fact:
sdb_index: "{{ [] + [ my_hosts.index(inventory_hostname) ] }}"
- debug:
msg: "{{ sdb_index|map('extract', efs_list)|list }}"

Resources