Get nodes in leaf host group - ansible

I'm trying to get all then nodes under a host group in order to fill a cluster variable in a template. My ansible hosts is something like this
[des_cns]
10.0.0.1
10.0.0.2
[des_rtc]
10.0.0.11
10.0.0.12
[des_ogl]
10.0.0.21
10.0.0.22
[des:children]
des_cns
des_rtc
des_ogl
[clustered:children]
des
When launching my playbook for 10.0.0.1, I can get with groups['des_cns'] all the nodes in this group (this is what i want) but seems like I'm unable to do so with variables. Whith group_names i get all groups in which this IP matches, but i only want the leaf group. I have managed to use a _ in all leaf groups for filtering purposes.
The closer i can get is (im sure im messing too much with this line):
{{groups[group_names|list|join(' ')|regex_findall('[a-z]*_[a_z]*')|to_json]}}
But that returns an error saying dict object has no attribute '[\"des_cns\"]'...
Thank you all for your help and sorry for my bad English.

Use select filter and search test:
{{ groups[ group_names | select('search','_') | first ] }}
Inner expression: selects only groups with _ character from group_names and take only first item out of it.

Related

AnsibleUnsafeText enhancement to an existing playbook

I have a playbook that includes a set of tasks to obtain the status of an IBM MQ Channel. The playbook is passed the Channel name and I run the runmqsc command on each server
and register the output which unfortunately is on many lines, so i need to get this onto one line
echo "DISPLAY CHS('{{CHANNEL_NAME}}') STATUS RQMNAME"| runmqsc {{QMGR}}|grep -v DISPLAY|sed 's/^[^ ].*$/%/g' | tr -s " " | tr -d "\n" | tr "%" "\n"|grep CHANNEL|sed 's/ CURRENT//g' | sed 's/^ //g'|sed 's/ *$//g'
which gave the output
CHANNEL(CHANNEL) CHLTYPE(CLUSSDR) CONNAME(1.2.3.4(1414)) RQMNAME(QMGR) STATUS(RUNNING) SUBSTATE(MQGET) XMITQ(XMIT_QUEUE)
this I parsed to create a list CHSstatus (when CHANNEL_NAME is a wildcard I can have more than one result). From this list I then set variables to the STATUS of the channel.
- set_fact:
allStopped: "{%if splitStatus.STATUS == 'STOPPED'%}{{allStopped + [splitStatus.CHANNEL]}}{%else%}{{allStopped}}{%endif%}"
allRunning: "{%if splitStatus.STATUS == 'RUNNING'%}{{allRunning + [splitStatus.CHANNEL]}}{%else%}{{allRunning}}{%endif%}"
mixedState: "{%if splitStatus.STATUS != 'STOPPED' and splitStatus.STATUS != 'RUNNING'%}{{mixedState + [splitStatus.CHANNEL]}}{%else%}{{mixedState}}{%endif%}"
with_items: "{{CHSstatus}}"
loop_control:
loop_var: splitStatus
This has been working fine for a while with no issues. The problem with this is that you actually need to know the Channel name and as I wanted to enhance it to get the status of all the channels in a CLUSTER I put a 'wrapper' task in front of it to collect the channel names and use these to pass them.
My playbook now calls the MQ command to get these channel names, we can pass the CLUSTER name and register the result in clusterChannels, and include the previously working task with each channel name
- name: Status of Cluster channels
include: channelstatus.yml CHANNEL_NAME={{item}}
with_items: "{{clusterChannels.stdout_lines}}"
This now fails when trying to access the CHSStatus list, I get the error is ERROR! Unexpected Exception: unhashable type: 'dict'
I have tried both methods and compared some variables for each call of the original tasks
Original
"CHSstatus = [{'STATUS': u'RUNNING', 'CHANNEL': u'CHANNEL1'}]",
"CHSstatus Type = list",
"CHANNEL_NAME = CHANNEL1",
"CHANNEL_NAME type = AnsibleUnicode"
with the wrapper
"CHSstatus = [{'STATUS': u'RUNNING', 'CHANNEL': u'CHANNEL1'}]",
"CHSstatus Type = list",
"CHANNEL_NAME = CHANNEL1",
"CHANNEL_NAME type = AnsibleUnsafeText"
I have tried to find information about what AnsibleUnsafeText variable is and found that
'Since you are registering the result of a command in a variable, Ansible can't know what will be the content which becomes delivered. Therefore the registered Text output is marked as Unsafe.'
This has confused me a bit as I am running two commands, one to get the list of Channels in the wrapper and another to get the status of each individual channel in the original unchanged code. The registered variable is of type list but when I pass each channel from the wrapper it becomes an AnsibleUnsafeText type, I noticed that each of the registered variables for both commands when run with_items do indeed have an AnsibleUnsafeText type.
Can i convert this in any way? I have seen answers on how to convert to int, and i have tried item|string but this did not work either.
My original playbook used roles and i created a cutdown version of this using tasks only, this included cut down versions of channelstatus.yml and this worked OK, i then converted this to a role again including the same file and this failed.
One thing i notices in both cases of my MQ command registered results gets parsed successfully but the error occurs when I try to use this list of dict.
Example below is a list which contains a single dict, this can be a list of multiple dict's when run
CHSstatus = [{'STATUS': u'RUNNING', 'CHANNEL': u'CHANNEL1'}]
and a simple debug command is enough for this error to fail the playbook...
- debug:
msg:
- "{{item.CHANNEL}}"
- "{{item.STATUS}}"
with_items: "{{CHSstatus}}"
ERROR! Unexpected Exception: unhashable type: 'dict'
however
- debug:
msg:
- "{{CHSstatus[0].CHANNEL}}"
- "{{CHSstatus[0].STATUS}}"
works fine, which really does not make sense
Any help appreciated

Why playbook take wrong values for group variables?

I have a problem with groups variables.
Example: I have two inventory groups group_A and group_B, and also have the same name files in group_vars:
inventories/
hosts.inv
[group_A]
server1
server2
[group_B]
server3
server4
group_vars/
group_A - file
var_port: 9001
group_B - file
var_port: 9002
The problem is when i execute:
ansible-playbook playbooks/playbook.yml -i inventories/hosts.inv -l group_B
playbook was executed for proper scope of servers (server3, server4) but it takes variables from group variables file group_A.
expected result: var_port: 9002
in realty : var_port: 9001
ansible 2.4.2.0
BR Oleg
I included ANSIBLE_DEBUG , and what i have found:
2018-05-03 15:23:23,663 p=129458 u=user | 129458 1525353803.66336: Loading data from /ansible/inventories/prod/group_vars/group_B.yml
2018-05-03 15:23:23,663 p=129458 u=user | 129661 1525353803.66060: in run() - task 00505680-eccc-d94e-2b1b-0000000000f4
2018-05-03 15:23:23,664 p=129458 u=user | 129661 1525353803.66458: calling self._execute()
2018-05-03 15:23:23,665 p=129458 u=user | 129458 1525353803.66589: Loading data from /ansible/inventories/prod/group_vars/group_A.yml
on playbook execution ansible scan all files with variables in folder group_vars which have variable "var_port", last will win.....
as you can found in another topic:
Ansible servers/groups in development/production
and from documentation:
http://docs.ansible.com/ansible/latest/user_guide/playbooks_variables.html#variable-precedence-where-should-i-put-a-variable
Note
Within any section, redefining a var will overwrite the previous instance. If multiple groups have the same variable, **the last one loaded wins**. If you define a variable twice in a play’s vars: section, the **2nd one wins**.
For me now NOT clear how to manage configuration files. In this case I must use unique variables names for each group, but it is not possible regarding roles, or should I use include_vars when i call playbook?
Super example how to manage variables files in multistage environment from DigitalOcean
How to Manage Multistage Environments with Ansible
I believe that the problem here, while not explicitly stated in the original question, is that Server{1,2} and Server{3,4} are actually the same servers in 2 different groups at the same level.
I ran into this problem which caused me to do some digging. I don't agree with it, but it is as designed. This was even fixed with full compatibility and the pull request was rejected
Discussion
Pull Request

Ansible using common user and pass to hosts

I've a hosts file with 4 host ip's and I'm always using "ansible_connection=ssh ansible_ssh_user=user ansible_ssh_pass=pass" besides host ip to verify the connection.
But, this is difficult to add these many times. Could someone please tell me where can I keep common these parameters and pass it to all my host ip's at a time?
Create file in directory all in directory group_vars with the content.
ansible_connection:ssh
ansible_ssh_user:user
ansible_ssh_pass:pass
It should work.

Ansible get column from stdout

I'm new to Ansible an thus this question may seem silly to more advanced users.
Anyway, I need to get the value 362496 for the column LDFree.
I know I can use the shell module with pipes and awk, but I was wondering if it's posisble to achive it in Ansible using some sort of "filter" for STDOUT?
This is the STDOUT from the CLI:
-------------------------(MB)-------------------------
CPG ---EstFree---- -------Usr------- ---Snp---- ---Adm---- -Capacity Efficiency-
Name RawFree LDFree Total Used Total Used Total Used Compaction Dedup
SSD_r6 483328 362496 12693504 12666880 12288 2048 8192 1024 1.0 -
You can done this knowing the fact that Ansible/Jinja support calling methods of native types:
- command: cat test.txt
register: cmd_res
- debug:
msg: "{{ cmd_res.stdout_lines[3].split()[2] }}"
stdout_lines[3] – take forth line, .split() – split it into tokens, [2] – take third token.

Complex filtering in Ansible/Jinja2

I am pretty new to Jinja2, and I'm wondering how to achieve this.
Say I have the following vars:
---
servers:
192.168.0.1:
names:
- foo.example.com
- foo
exports:
data:
foo1: /disks/foo1
foo2: /disks/foo2
192.168.0.2:
...
I want to create a symlink /data/foo1 to /disks/foo1 and /data/foo2 to /disks/foo2, but only on foo server; on other servers, make symlinks to their respective exports. So I thought file status=link with_items=... would be the correct thing to do. In Python, I can get the array I need using the following logic:
[
{ 'mount': mount, 'export': export }
for ip, server in servers.iteritems()
if ansible_hostname in server['names']
and 'exports' in server
and 'data' in server['exports']
for mount, export in server['exports']['data'].iteritems()'
]
I don't know how to do this in Jinja2. I wanted to do something like
{{ servers | select('ansible_hostname in self.names') | ... }}
but that doesn't work. Would I need to create a plugin for this logic? Or is my approach all wrong and I should rethink the structure of my servers data?
Answer from my comment:
Usually you want to use inventory_hostname variable – it is what you use as host name in inventory.
servers[ansible_hostname] will access servers' key with name of ansible_hostname's value.
Just for curiosity, you can check out this (complex filter chain) and this (runtime object construction).

Resources