Ansible only run when subelement of variable exists - ansible

I have some variables defined like this:
x_php_versions_installed:
php70:
- php70-curl
- php70-xml
- php70-xmlrpc
- php70-zip
- pecl-memcached
php71:
- php71-curl
- php71-xml
- php71-xmlrpc
- php71-zip
php72:
- php72-curl
- php72-xml
- php72-xmlrpc
- php72-zip
- pecl-memcached
And I would like to check all of the vars (php70, php71, php72 and so on) has this variable: pecl-memcached and if one has, then run a command. My playbook looks like this:
- name: memcached pecl install
pear:
executable: '/usr/local/{{ item }}/bin/pecl'
name: 'pecl/memcached'
state: 'latest'
with_items: '{{ x_php_versions_installed | list }}'
when: 'item.pecl-memcached is defined'
this should call the /usr/local/php70/bin/pecl and /usr/local/php72/bin/pecl binary to install memcached. As soon as I remove the when condition, it works very well, but it will call every variable inside x_php_versions_installed not only where pecl-memcached is defined. So I need to fix the when condition in this case, but all of my tries are gives me an error.

If you want your when to check a list properly, you'll have to use the test operator in of Jinja:
- name: memcached pecl install
pear:
executable: '/usr/local/{{ item }}/bin/pecl'
name: 'pecl/memcached'
state: 'latest'
with_items: '{{ x_php_versions_installed | list }}'
when: "'pecl-memcached' in x_php_versions_installed[item]"
Given the playbook:
- hosts: localhost
gather_facts: no
vars:
x_php_versions_installed:
php70:
- php70-curl
- php70-xml
- php70-xmlrpc
- php70-zip
- pecl-memcached
php71:
- php71-curl
- php71-xml
- php71-xmlrpc
- php71-zip
php72:
- php72-curl
- php72-xml
- php72-xmlrpc
- php72-zip
- pecl-memcached
tasks:
- debug:
msg: "{{ item }}"
with_items: "{{ x_php_versions_installed | list }}"
when: "'pecl-memcached' in x_php_versions_installed[item]"
The recap would be:
TASK [debug] *******************************************************************
ok: [localhost] => (item=php70) => {
"msg": "php70"
}
skipping: [localhost] => (item=php71)
ok: [localhost] => (item=php72) => {
"msg": "php72"
}
Your subelement is a list not a dictionary, so accessing an element key like your are trying here, i.e.:
when: "x_php_versions_installed[item]['pecl-memcached'] is defined"
would work on a dictionary like this one:
x_php_versions_installed:
php70:
php70-curl:
php70-xml:
php70-xmlrpc:
php70-zip:
pecl-memcached:
# same goes for the other versions of PHP
Given the playbook:
- hosts: localhost
gather_facts: no
vars:
x_php_versions_installed:
php70:
php70-curl:
php70-xml:
php70-xmlrpc:
php70-zip:
pecl-memcached:
php71:
php71-curl:
php71-xml:
php71-xmlrpc:
php71-zip:
php72:
php72-curl:
php72-xml:
php72-xmlrpc:
php72-zip:
pecl-memcached:
tasks:
- debug:
msg: "{{ item }}"
with_items: "{{ x_php_versions_installed | list }}"
when: "x_php_versions_installed[item]['pecl-memcached'] is defined"
The recap would be:
TASK [debug] *******************************************************************
ok: [localhost] => (item=php70) => {
"msg": "php70"
}
skipping: [localhost] => (item=php71)
ok: [localhost] => (item=php72) => {
"msg": "php72"
}

Related

Unique values from ansible output dict's

I have some servers with a lot of wordpress instances, who I ask them what versions they have.
- name: CONTADOR WP VERSIONES
shell: mycommand
register: wp_versions
- debug: msg: "{{ wp_versions.stdout_lines }}"
For example:
TASK [debug] *********************************************************************
ok: [server1] => {
"msg": [
"5.1.13"
]
}
ok: [server2] => {
"msg": [
"5.1.12",
"5.1.13"
]
}
ok: [server3] => {
"msg": [
"5.1.10",
"5.1.13",
]
}
I need to list a unique values like this:
"msg": [
"5.1.10",
"5.1.12",
"5.1.13",
]
I have tried all that i found but nothing works as I want.
Thanks
Use special variable ansible_play_hosts and extract the variables from the hostvars
- set_fact:
all_vers: "{{ ansible_play_hosts|
map('extract', hostvars, ['wp_versions', 'stdout_lines'])|
flatten|unique }}"
run_once: true
gives
all_vers:
- 5.1.13
- 5.1.12
- 5.1.10
You could do something like this:
- hosts: all
gather_facts: false
tasks:
- name: CONTADOR WP VERSIONES
shell: mycommand
register: wp_versions
- hosts: localhost
gather_facts: false
tasks:
# This tasks builds a flattened list of all the
# wp_versions.stdout_lines values collected from your hosts.
- name: Collect wp_versions information
set_fact:
all_wp_versions_pre: "{{ all_wp_versions_pre + hostvars[item].wp_versions.stdout_lines }}"
loop: "{{ groups.all }}"
vars:
all_wp_versions_pre: []
# Here we use the `unique` filter to produce a list of
# unique versions.
- name: Set all_wp_versions fact
set_fact:
all_wp_versions: "{{ all_wp_versions_pre|unique }}"
- debug:
var: all_wp_versions
Given you examples, this would produce the following output:
TASK [debug] ********************************************************************************************
ok: [localhost] => {
"all_wp_versions": [
"5.1.13",
"5.1.12",
"5.1.10"
]
}

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 flipping inventory still reads in the same order

I am working on a project aimed at populating the IP's of some routers based on East/West locations. The first host will always be the primary and the second will always be the secondary.
Based on the location passed, I flip the inventory. I see the inventory being flipped, but Ansible get the value from the list in the same order.
It doesn't matter what order the inventory list is read. I need for the first host to read the first element e.g. 20.21.22.23 and then the second host to read the second element 28.29.30.31.
Right now, ATL is always the first element and LAX the second.
ok: [ATL_isr_lab] => {
"msg": [
"20.21.22.23",
"24.25.26.27",
"24.25.26.28"
]
}
ok: [LAX_isr_lab] => {
"msg": [
"28.29.30.31",
"32.33.34.35",
"32.33.34.36"
]
}
------------------ Inventory Flipped -------------------------------
ok: [LAX_isr_lab] => {
"msg": [
"28.29.30.31",
"32.33.34.35",
"32.33.34.36"
]
}
ok: [ATL_isr_lab] => {
"msg": [
"20.21.22.23",
"24.25.26.27",
"24.25.26.28"
]
}
---
- hosts: test_hosts
vars:
region: east
_Hub_IP: [ 20.21.22.23, 28.29.30.31]
_Transit_IP: [ 24.25.26.27, 32.33.34.35]
_Neighbor_IP: [24.25.26.28, 32.33.34.36]
_idx: "{{ groups.all.index(inventory_hostname) }}"
#flips inventory if west
order: "{{ (region == 'east')|ternary('reverse_inventory', 'inventory') }}"
become: yes
ignore_unreachable: true
gather_facts: false
tasks:
- name: "Configure Router"
debug:
msg:
- "{{ _Hub_IP[_idx|int] }}"
- "{{ _Transit_IP[_idx|int] }}"
- "{{ _Neighbor_IP[_idx|int] }}"
Well, the issue is not coming with the reverse_inventory and inventory value of the order parameter like you seems to think it is.
The issue is to think that groups.all is indeed reversed when you do use the reverse_inventory value.
Here is an example of this, with the playbook:
- hosts: localhost
gather_facts: no
order: "{{ (region == 'east')|ternary('reverse_inventory', 'inventory') }}"
tasks:
- debug:
var: groups.all
Running it with, with the region as an extra-vars:
ansible-playbook play.yml --inventory inventory.yml --extra-vars "region=east"
Will yield:
ok: [localhost] =>
groups.all:
- LAX_isr_lab
- ATL_isr_lab
ansible-playbook play.yml --inventory inventory.yml --extra-vars "region=west"
Will yield:
ok: [localhost] =>
groups.all:
- LAX_isr_lab
- ATL_isr_lab
Still the sorting works, see:
- hosts: all
gather_facts: no
order: "{{ (region == 'east')|ternary('reverse_inventory', 'inventory') }}"
tasks:
- debug:
Run with:
ansible-playbook play.yml --inventory inventory.yml --extra-vars "region=east"
Will yield
ok: [ATL_isr_lab] =>
msg: Hello world!
ok: [LAX_isr_lab] =>
msg: Hello world!
ansible-playbook play.yml --inventory inventory.yml --extra-vars "region=west"
Will yield
ok: [LAX_isr_lab] =>
msg: Hello world!
ok: [ATL_isr_lab] =>
msg: Hello world!
So, what ends up being wrong is your _idx value.
To fix this, you could use the reverse filter of jinja with the same ternary as you are using in the order parameter, like this:
_idx: "{{ ((region == 'east')|ternary(groups.all|reverse, groups.all)).index(inventory_hostname) }}"
Working playbook:
- hosts: all
gather_facts: no
order: "{{ (region == 'east')|ternary('reverse_inventory', 'inventory') }}"
vars:
_Hub_IP: [20.21.22.23, 28.29.30.31]
_Transit_IP: [24.25.26.27, 32.33.34.35]
_Neighbor_IP: [24.25.26.28, 32.33.34.36]
_idx: "{{ ((region == 'east')|ternary(groups.all|reverse, groups.all)).index(inventory_hostname) }}"
tasks:
- debug:
msg:
- "{{ _Hub_IP[_idx|int] }}"
- "{{ _Transit_IP[_idx|int] }}"
- "{{ _Neighbor_IP[_idx|int] }}"
Running examples:
ansible-playbook play.yml --inventory inventory.yml --extra-vars "region=east"
Will yield:
ok: [ATL_isr_lab] =>
msg:
- 20.21.22.23
- 24.25.26.27
- 24.25.26.28
ok: [LAX_isr_lab] =>
msg:
- 28.29.30.31
- 32.33.34.35
- 32.33.34.36
ansible-playbook play.yml --inventory inventory.yml --extra-vars "region=west"
Will yield:
ok: [LAX_isr_lab] =>
msg:
- 20.21.22.23
- 24.25.26.27
- 24.25.26.28
ok: [ATL_isr_lab] =>
msg:
- 28.29.30.31
- 32.33.34.35
- 32.33.34.36
Got it working as posted originally. I had to upgrade to ansible version 2.11.6. I'm running Debian 10 and apt-get update/apt-get upgrade did not find a newer version.
My solution involved deleting the version and installing it again through pip. After that, I ran the code and it worked flawlessly.

Processing embedded lists inside dict variables in Ansible

I got the example code in Ansible:
- name: testing
hosts: localhost
vars:
svc:
https: ['tcp/443']
app_svc: ['tcp/5543', 'udp/5543', 'tcp/3100']
tasks:
- name: print
debug:
msg: port={{item.key}} value={{item.value}}
with_dict:
- "{{svc}}"
And this outputs to:
ok: [127.0.0.1] => (item=None) => {
"msg": "port=app_svc value=[u'tcp/5543', u'udp/5543', u'tcp/3100']"
}
ok: [127.0.0.1] => (item=None) => {
"msg": "port=https value=[u'tcp/443']"
}
What I would like to achieve is, when there is more than one element in list of values it would split like this:
- name=https, prot=tcp, port=443
- name=app_svc, prot=tcp, port=5543
- name=app_svc, prot=udp, port=5543
- name=app_svc, prot=tcp, port=3100
with_dict stanza only displays me a whole list and I couldn't find a way do do it differently. Is it possible to do it like that without reorganizing the var section? Thanks in advance for input.
To see the syntax errors run
# ansible-lint <YOUR-PLAYBOOK>
Correct syntax should be
- hosts: localhost
gather_facts: no
vars:
svc:
https: ['tcp/443']
app_svc: ['tcp/5543', 'udp/5543', 'tcp/3100']
tasks:
- name: print
debug:
msg: "port={{ item.key }} value={{ item.value }}"
with_dict: "{{ svc }}"
gives
"msg": "port=app_svc value=[u'tcp/5543', u'udp/5543', u'tcp/3100']"
"msg": "port=https value=[u'tcp/443']"
Change the loop
- name: print
debug:
msg: "name={{ item.0.key }},
prot={{ item.1.split('/')[0] }},
port={{ item.1.split('/')[1] }}"
loop: "{{ lookup('subelements', svc|dict2items, 'value') }}"
to get the format
"msg": "name=app_svc, prot=tcp, port=5543"
"msg": "name=app_svc, prot=udp, port=5543"
"msg": "name=app_svc, prot=tcp, port=3100"
"msg": "name=https, prot=tcp, port=443"
dict2items is available since version 2.6 . Without "dict2items" transform the data first. See below (not tested).
https:
- {key: 'https', value: ['tcp/443']}
- {key: 'app_svc', value: ['tcp/5543', 'udp/5543', 'tcp/3100']}

Ansible : error in calling the groups through restapi

I am trying fetch to distinct groups in the inventory through a variable.this is the command am trying to run in the playbook to add hosts to the Nagios XI. Am trying to do this using Rest API through CURL command. Am getting error as Incorrect pattern. Can some one please advise about the issue. or help me out with how we can call two groups from the inventory in the same command.
- name: add host to nagios XI.
shell: curl -XPOST "http://16.231.22.60/nagiosxi/api/v1/config/host?apikey=qfOQpKFORCNo7HPunDUsSjW7f2rNNmrdVv3kvYpmQcNdSS2grV2jeXKsgbv3QgfL&pretty=1" -d "host_name={{ item.hostname }}&address={{ item.address }}&use=xiwizard_ncpa_host&max_check_attempts=5&check_period=xi_timeperiod_24x7&notification_interval=60&notification_period=xi_timeperiod_24x7&notifications_enabled=0&contacts=nagiosadmin&contact_groups=Candle Admins,Candle-L1-L2-Internal&applyconfig=1"
with_items:
- { hostname: "{{ groups['grp1'] }}", address: "{{ groups['grp2'] }}"}
EDIT: code formatting
Understanding that your hostname and address from each group match each other you can do the following:
Inventory:
[grp1]
host1
host2
host3
[grp2]
10.100.10.1
10.100.10.2
10.100.10.3
Play:
---
- name: Debug Together
hosts: localhost
gather_facts: False
tasks:
- name: Add host to nagios XI
shell: shell: curl -XPOST "http://16.231.22.60/nagiosxi/api/v1/config/host?apikey=qfOQpKFORCNo7HPunDUsSjW7f2rNNmrdVv3kvYpmQcNdSS2grV2jeXKsgbv3QgfL&pretty=1" -d "host_name={{ item.0 }}&address={{ item.1 }}&use=xiwizard_ncpa_host&max_check_attempts=5&check_period=xi_timeperiod_24x7&notification_interval=60&notification_period=xi_timeperiod_24x7&notifications_enabled=0&contacts=nagiosadmin&contact_groups=Candle Admins,Candle-L1-L2-Internal&applyconfig=1"
with_together:
- "{{ groups['grp1'] }}"
- "{{ groups['grp2'] }}"
You will get something like:
TASK [debug] ******************************************************************************************************************
ok: [localhost] => (item=None) => {
"item.0, item.1": "(u'host1', u'10.100.10.1')"
}
ok: [localhost] => (item=None) => {
"item.0, item.1": "(u'host2', u'10.100.10.2')"
}
ok: [localhost] => (item=None) => {
"item.0, item.1": "(u'host3', u'10.100.10.3')"
}
Comming from my test:
- name:
debug:
var: item.0, item.1
with_together:
- "{{ groups['grp1'] }}"
- "{{ groups['grp2'] }}"

Resources