passing numeric parameter on ansible variable array - ansible

I got this ansible variable array, weblogic[1].name which will give me the name of the second array "manageServer1".
weblogic: [
{
name: "adminServer"
address: "1.1.1.1"
port: 1701
ssl: 1702
},
{
name: "manageServer1"
address: "1.1.1.2"
port: 1703
ssl: 1704
},
]
How can I pass parameter x=1 on the array, this one won't work, weblogic[x].name or weblogic['x'].name?
I'm working on Ansible 2.6-2.7.

Commas are missing in the lists. See example below.
> cat test.yml
---
- hosts: localhost
gather_facts: no
vars:
weblogic:
- { name: "adminServer", address: "1.1.1.1", port: 1701, ssl: 1702 }
- { name: "manageServer1", address: "1.1.1.2", port: 1703, ssl: 1704 }
tasks:
- debug: var=weblogic[item].name
loop:
- 0
- 1
> ansible-playbook test.yml | grep weblogic
"weblogic[item].name": "adminServer"
"weblogic[item].name": "manageServer1"

Related

yml format for inventory file...fails

---
all:
zones:
- name: accessswitch
hosts:
- name: accessswitch-x0
ip: 192.168.4.xx
- name: groupswitch
hosts:
- name: groupswitch-x1
ip: 192.168.4.xx
- name: groupswitch-x2
ip: 192.168.4.xx
- name: groupswitch-x3
ip: 192.168.4.xx
Basically i have a access switch and to that switch there are many group switches connected to it...Tried already "children" but this does not work. A typical ini file works....
Also i have some variables...which do apply to all zones aka. access witch & group switches...
in the future there will be more than 1 ..multiple access switches..
....
some docs use ansible-host:...confusing..
And yes..checked the uml structure...
cat#catwomen:~/workspace/ansible-simulator/inventories/simulator/host_vars$ sudo ansible -i testdata.yaml all -m ping
[WARNING]: provided hosts list is empty, only localhost is available. Note that the implicit localhost does not match
'all'
cat#catwomen:~/workspace/ansible-simulator/inventories/simulator/host_vars$ sudo ansible -i testdata.yaml all -m ping
[WARNING]: provided hosts list is empty, only localhost is available. Note that the implicit localhost does not match
'all'
You have yaml indentation problems which is an error (indentation in yaml is significant). Moreover, you must follow the specific format described in the documentation.
Very basically, the file is an imbrication of groups in parent/child relationship starting with the top element special group all. A group definition looks like:
---
my_group1: # group name, use `all` at top level
vars:
# definition of vars for this group. Apply to all hosts if defined for `all`
hosts:
# hosts in that group (host is ungrouped if it appears only in `all`)
children:
# mapping of children group definitions (repeat the current format)
A host definition looks like:
my_host1:
# definition of vars specific to that host if any
A var definition (either for vars in group or for a specific host) looks like:
my_variable1: some value
If I understand correctly from your example, here is how your yaml inventory should look like (inventories/so_example.yml)
---
all:
children:
accessswitch:
hosts:
accessswitch-x0:
ansible_host: 192.168.4.xx
groupswitch:
hosts:
groupswitch-x1:
ansible_host: 192.168.4.xx
groupswitch-x2:
ansible_host: 192.168.4.xx
groupswitch-x3:
ansible_host: 192.168.4.xx
You can then easilly see how the above is interpreted with the ansible-inventory command:
$ ansible-inventory -i inventories/so_example.yml --graph
#all:
|--#accessswitch:
| |--accessswitch-x0
|--#groupswitch:
| |--groupswitch-x1
| |--groupswitch-x2
| |--groupswitch-x3
|--#ungrouped:
$ ansible-inventory -i inventories/so_example.yml --list
{
"_meta": {
"hostvars": {
"accessswitch-x0": {
"ansible_host": "192.168.4.xx"
},
"groupswitch-x1": {
"ansible_host": "192.168.4.xx"
},
"groupswitch-x2": {
"ansible_host": "192.168.4.xx"
},
"groupswitch-x3": {
"ansible_host": "192.168.4.xx"
}
}
},
"accessswitch": {
"hosts": [
"accessswitch-x0"
]
},
"all": {
"children": [
"accessswitch",
"groupswitch",
"ungrouped"
]
},
"groupswitch": {
"hosts": [
"groupswitch-x1",
"groupswitch-x2",
"groupswitch-x3"
]
}
}
The incident is not correct for the line #6. Try below please.
---
all:
zones:
- name: accessswitch
hosts:
- name: accessswitch-x0
ip: 192.168.4.xx
- name: groupswitch
hosts:
- name: groupswitch-x1
ip: 192.168.4.xx
- name: groupswitch-x2
ip: 192.168.4.xx
- name: groupswitch-x3
ip: 192.168.4.xx

Loop over nested dict

I need to iterate over a complex dict and apply properties from one key to another
I have tried many possibilities, such as with_nested, and with_subelements
I have an object as such
group_hosts:
- group:
name: a
hosts:
- host1
- host2
ports:
- 22
- 80
- group:
name: b
hosts:
- host3
- host4
ports:
- 22
- 80
And, given a host, I need to associate all the ports to it (host1 will have ports 22 and 80 associated to is, for example), so that in a later moment, while iterating I can use the wait_for module to check if the ports are open
The only workaround I found was to repeat the hostname for how many ports it had (by so doing I removed an extra list to loop)
More explicitly my var object became this
group_hosts:
- group:
name: a
hosts:
- name: host1
port: 80
- name: host1
port: 22
- name: host2
port: 22
- name: host2
port: 80
- group:
name: b
hosts:
- name: host3
port: 20
- name: host4
port: 2222
And my play this:
- name: traverse dict
debug:
msg: "group: {{item.0.group.name}} host is: {{item.1.name}} port is: {{item.1.port}}"
loop: "{{ group_hosts | subelements('group.hosts') | list }}"
But I don't like this workaround as I've had to modify the dict object by writing it in a less efficient way.
So, given the first dict object, how can I loop hosts and associate ports to them?
Meaning:
I want that given host1, I check its 22 and 80 ports, host 2 the same.
So:
hosts group a:
host1 : check ports 22, 80
host2: check ports 22, 80
hosts group b:
same as above
I already know how to "check" the ports on some host, my question is how to iterate such an object
Let's simplify the dictionary. The tasks below
- set_fact:
hosts2: "{{ hosts2|default({})|
combine({item.1:
{'name': item.0.group.name,
'ports': item.0.group.ports}}) }}"
loop: "{{ lookup('subelements', group_hosts, 'group.hosts') }}"
- debug:
var: hosts2
give a dictionary that can be easily iterated.
"hosts2": {
"host1": {
"name": "a",
"ports": [
22,
80
]
},
"host2": {
"name": "a",
"ports": [
22,
80
]
},
"host3": {
"name": "b",
"ports": [
22,
80
]
},
"host4": {
"name": "b",
"ports": [
22,
80
]
}
}

Merge lists in Ansible when a key/value pair from each matches

I have two lists of dictionaries (port_info and int_trunk) that I am trying to merge when the value of the 'port' key matches however not every port will be represented in 'int_trunk'. In this instance, just leave the 'trunked_vlans' value blank. I am using 'with_nested' to iterate over the lists. I can't seem to get the output to do what I want. I have tried the combine() filter along with manually trying to create vars but can't quite get it. How do I get these two lists to combine?
---
- hosts: localhost
connection: local
gather_facts: no
vars:
trunk_ports: []
non_trunk_ports: []
new_port_info: []
port_info:
- desc: "*** Voice Server Port ***"
duplex: "a-full"
port: "Gi1/0/1"
speed: "a-1000"
status: connected
trunk_vlans: ""
type: "10/100/1000BaseTX"
vlan: 3
- desc: "*** Voice Server Port ***"
duplex: "a-full"
port: "Gi1/0/2"
speed: "a-1000"
status: connected
trunk_vlans: ""
type: "10/100/1000BaseTX"
vlan: 3
- desc: "Some Port"
duplex: auto
port: "Gi1/0/3"
speed: auto
status: notconnect
trunk_vlans: ""
type: "10/100/1000BaseTX"
vlan: 23
int_trunk:
- port: "Gi1/0/1"
vlans: "1-50"
- port: "Gi1/0/2"
vlans: "50-60"
The output of this should be:
port_info:
- desc: "*** Voice Server Port ***"
duplex: "a-full"
port: "Gi1/0/1"
speed: "a-1000"
status: connected
trunk_vlans: "1-50"
type: "10/100/1000BaseTX"
vlan: 3
- desc: "*** Voice Server Port ***"
duplex: "a-full"
port: "Gi1/0/2"
speed: "a-1000"
status: connected
trunk_vlans: "50-60"
type: "10/100/1000BaseTX"
vlan: 3
- desc: "Some Port"
duplex: auto
port: "Gi1/0/3"
speed: auto
status: notconnect
trunk_vlans: ""
type: "10/100/1000BaseTX"
vlan: 23
This is one of the things I have tried:
---
- hosts: localhost
connection: local
gather_facts: no
vars:
trunk_ports: []
non_trunk_ports: []
new_port_info: []
port_info:
- desc: "*** Voice Server Port ***"
duplex: "a-full"
port: "Gi1/0/1"
speed: "a-1000"
status: connected
trunk_vlans: ""
type: "10/100/1000BaseTX"
vlan: 3
- desc: "*** Voice Server Port ***"
duplex: "a-full"
port: "Gi1/0/2"
speed: "a-1000"
status: connected
trunk_vlans: ""
type: "10/100/1000BaseTX"
vlan: 3
- desc: " "
duplex: auto
port: "Gi1/0/3"
speed: auto
status: notconnect
trunk_vlans: ""
type: "10/100/1000BaseTX"
vlan: 23
int_trunk:
- port: "Gi1/0/1"
vlans: "1-50"
- port: "Gi1/0/2"
vlans: "50-60"
tasks:
- name: Merge trunk ports
set_fact:
trunk_ports: "{{ trunk_ports + [ { 'desc': item.0.desc, 'duplex': item.0.duplex, 'port': item.0.port, 'speed': item.0.speed, 'status
': item.0.status, 'trunk_vlans': item.1.vlans, 'type': item.0.type, 'vlan': item.0.vlan } ] }}"
with_nested:
- "{{ port_info }}"
- "{{ int_trunk }}"
when: item.0.port == item.1.port
- name: Merge non-trunk ports
set_fact:
non_trunk_ports: "{{ non_trunk_ports + [ { 'desc': item.0.desc, 'duplex': item.0.duplex, 'port': item.0.port, 'speed': item.0.speed,
'status': item.0.status, 'trunk_vlans': item.0.trunk_vlans, 'type': item.0.type, 'vlan': item.0.vlan } ] }}"
with_nested:
- "{{ port_info }}"
- "{{ int_trunk }}"
when: item.0.port != item.1.port
- name: Merge all ports
set_fact:
new_port_info: "{{ new_port_info + [item.0|combine(item.1)] }}"
with_nested:
- "{{ port_info }}"
- "{{ trunk_ports }}"
when: item.0.port == item.1.port
- name: Echo
debug: var=trunk_ports
- name: Echo
debug: var=non_trunk_ports
- name: Echo
debug: var=new_port_info
I end up with ports Gi1/0/1 and Gi1/0/2 but not Gi1/0/3.
Not sure if there is a "one line" filtering that could do this. Here is something i came up with, explanations of the flow first:
we populate a list of all ports that are common in the 2 lists, using 2 set_fact steps.
we parse the 2 lists together, and when the element from port_info has port attribute equal to the element from int_trunk, we combine/merge the 2 dictionaries.
merge of common entries is done. now we need to parse the port_info list once again, and add to the port_info_final list all elements that have no matching element in the int_trunk.
We got both merged elements and unique elements in the port_info_final list.
playbook:
---
- hosts: localhost
connection: local
gather_facts: false
vars:
trunk_ports: []
non_trunk_ports: []
new_port_info: []
port_info:
- desc: "*** Voice Server Port ***"
duplex: "a-full"
port: "Gi1/0/1"
speed: "a-1000"
status: connected
trunk_vlans: ""
type: "10/100/1000BaseTX"
vlan: 3
- desc: "*** Voice Server Port ***"
duplex: "a-full"
port: "Gi1/0/2"
speed: "a-1000"
status: connected
trunk_vlans: ""
type: "10/100/1000BaseTX"
vlan: 3
- desc: "Some Port"
duplex: auto
port: "Gi1/0/3"
speed: auto
status: notconnect
trunk_vlans: ""
type: "10/100/1000BaseTX"
vlan: 23
int_trunk:
- port: "Gi1/0/1"
vlans: "1-50"
- port: "Gi1/0/2"
vlans: "50-60"
port_info_final: []
tasks:
- name: get the lists of ports per list
set_fact:
portlist_1: "{{ port_info | map(attribute='port') | list }}"
portlist_2: "{{ int_trunk | map(attribute='port') | list }}"
- name: get the ports that exist in port_info but not in int_trunk
set_fact:
ports_not_in_int_trunk: "{{ portlist_1 | difference(portlist_2) }}"
- name: merge the dictionaries when the port is matched
set_fact:
port_info_final: "{{ port_info_final + [item[0] | combine(item[1])] }}"
when: item[0].port == item[1].port
loop: "{{ query('nested', int_trunk, port_info) }}"
- name: add all the port_info elements that dont have entry in int_trunk
set_fact:
port_info_final: "{{ port_info_final + [item] }}"
when: item.port in ports_not_in_int_trunk
loop: "{{ port_info }}"
- name: print results
debug:
msg: "{{ port_info_final }}"
result:
TASK [print results] ************************************************************************************************************************************************************************************************
ok: [localhost] => {
"msg": [
{
"desc": "*** Voice Server Port ***",
"duplex": "a-full",
"port": "Gi1/0/1",
"speed": "a-1000",
"status": "connected",
"trunk_vlans": "",
"type": "10/100/1000BaseTX",
"vlan": 3,
"vlans": "1-50"
},
{
"desc": "*** Voice Server Port ***",
"duplex": "a-full",
"port": "Gi1/0/2",
"speed": "a-1000",
"status": "connected",
"trunk_vlans": "",
"type": "10/100/1000BaseTX",
"vlan": 3,
"vlans": "50-60"
},
{
"desc": "Some Port",
"duplex": "auto",
"port": "Gi1/0/3",
"speed": "auto",
"status": "notconnect",
"trunk_vlans": "",
"type": "10/100/1000BaseTX",
"vlan": 23
}
]
}
PLAY RECAP **********************************************************************************************************************************************************************************************************
localhost : ok=5 changed=0 unreachable=0 failed=0
hope it helps

Ansible: Updating values in a list of dictionaries

In my payload, I have a variable that is actually a list of dictionaries, such as this one:
myvar:
- name: name1
ip_addresses:
- 10.10.10.10
- 11.11.11.11
nat_destination_addresses:
- host: 12.12.12.12
destination: 13.13.13.13
- host: 14.14.14.14
destination: 15.15.15.15
nat_source_address: 16.16.16.16
applications:
- protocol: tcp
port: 8302
- protocol: udp
port: 2000
- protocol: tcp
port: 2000-5600
- name: name2
ip_addresses:
- 17.17.17.17
- name: name3
ip_addresses:
- 18.18.18.18
- 19.19.19.19
All the values for each element in myvar are optional, except for the name, which is mandatory.
I am trying to pad the ip addresses (ip_addresses, nat_destination_addresses and nat_source_address) and ports. The ports should have a length of five characters with zeroes at the beginning (2000 becomes 02000 and 2000-5600 becomes 02000-05600) and the ip addresses should have three characters for each subsection (18.18.18.18 becomes 018.018.018.018).
The problem that I have is that I am not able to change only subsections of myvar.
I have read other questions here, such as:
merging dictionaries in ansible
Using set_facts and with_items together in Ansible
But to no avail. No matter what I do, I am not able to keep the original dictionary, I end up with a list of ip_addresses if I use the combine filter from the second StackOverflow link.
The expected result is the original myvar variable with updated ip addresses and ports.
This seems like a good time to throw your logic into a custom Ansible module. It doesn't have to be anything fancy, for example:
from ansible.module_utils.basic import AnsibleModule
def pad_addr(addr):
return '.'.join('%03d' % int(x) for x in addr.split('.'))
def main():
module_args = dict(
data=dict(type='list', required=True),
)
module = AnsibleModule(
argument_spec=module_args,
supports_check_mode=True
)
data = module.params['data']
for d in data:
if 'ip_addresses' in d:
d['ip_addresses'] = [pad_addr(x) for x in d['ip_addresses']]
if 'nat_destination_addresses' in d:
for dest in d['nat_destination_addresses']:
dest['host'] = pad_addr(dest['host'])
dest['destination'] = pad_addr(dest['destination'])
if 'nat_source_address' in d:
d['nat_source_address'] = pad_addr(d['nat_source_address'])
if 'applications' in d:
for service in d['applications']:
service['port'] = '%05d' % service['port']
module.exit_json(changed=False,
result=data)
if __name__ == '__main__':
main()
If I drop the above into library/pad_data.py and then run the following playbook:
- hosts: localhost
gather_facts: false
vars:
myvar:
- name: name1
ip_addresses:
- 10.10.10.10
- 11.11.11.11
nat_destination_addresses:
- host: 12.12.12.12
destination: 13.13.13.13
- host: 14.14.14.14
destination: 15.15.15.15
nat_source_address: 16.16.16.16
applications:
- protocol: tcp
port: 8302
- protocol: udp
port: 2000
- protocol: tcp
port: 2000
- name: name2
ip_addresses:
- 17.17.17.17
- name: name3
ip_addresses:
- 18.18.18.18
- 19.19.19.19
tasks:
- pad_data:
data: "{{ myvar }}"
register: padded
- debug:
var: padded.result
I get as the result:
TASK [debug] *******************************************************************
ok: [localhost] => {
"padded.result": [
{
"applications": [
{
"port": "08302",
"protocol": "tcp"
},
{
"port": "02000",
"protocol": "udp"
},
{
"port": "02000",
"protocol": "tcp"
}
],
"ip_addresses": [
"010.010.010.010",
"011.011.011.011"
],
"name": "name1",
"nat_destination_addresses": [
{
"destination": "013.013.013.013",
"host": "012.012.012.012"
},
{
"destination": "015.015.015.015",
"host": "014.014.014.014"
}
],
"nat_source_address": "016.016.016.016"
},
{
"ip_addresses": [
"017.017.017.017"
],
"name": "name2"
},
{
"ip_addresses": [
"018.018.018.018",
"019.019.019.019"
],
"name": "name3"
}
]
}
Larsks' answer was on point and is probably the best solution for most people, but my requirements are to limit the number of modules created with Python for this project, so here is my workaround for reference purposes.
Basically, what I do in this sample is:
Locally:
I take myvar, I output it to a yml file (with '---' at the top of the file and making sure that myvar is still set as the key.
Using regexp and the replace module, I replace the parts of the file that I want to replace.
On all of my hosts:
I reload the (now) properly formatted myvar and replace the old myvar variable using include_vars
---
- name: Customer {{ customer_id }} - Format the ip addresses and ports
hosts: localhost
gather_facts: no
connection: local
tags: [format_vars]
tasks:
- name: Copy the 'myvar' content to a local file to allow ip addresses
and ports formatting
copy:
content: "---\n{{ { 'myvar': myvar} | to_nice_yaml(indent=2) }}"
dest: "{{ formatted_myvar_file }}"
- name: Pad all ip addresses parts with two zeroes to ensure that all parts have at least three numbers
replace:
path: "{{ formatted_myvar_file }}"
regexp: '(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})'
replace: '00\1.00\2.00\3.00\4'
- name: Remove extra zeroes from ip addresses to ensure that all of their parts have exactly three numbers
replace:
path: "{{ formatted_myvar_file }}"
regexp: '\d{0,2}(\d{3})\.\d{0,2}(\d{3})\.\d{0,2}(\d{3})\.\d{0,2}(\d{3})'
replace: '\1.\2.\3.\4'
- name: Pad all ports with four zeroes to ensure that they all have at least five numbers
replace:
path: "{{ formatted_myvar_file }}"
regexp: 'port: (\d{1,5})'
replace: 'port: 0000\1'
- name: Remove extra zeroes from ports to ensure that they all have exactly five numbers
replace:
path: "{{ formatted_myvar_file }}"
regexp: 'port: \d{0,4}(\d{5})'
replace: 'port: \1'
- name: Pad all second parts of port ranges with four zeroes to ensure that they all have at least five numbers
replace:
path: "{{ formatted_myvar_file }}"
regexp: 'port: (\d{5})-(\d{1,5})'
replace: 'port: \1-0000\2'
- name: Remove extra zeroes from second parts of port ranges to ensure that they all have exactly five numbers
replace:
path: "{{ formatted_myvar_file }}"
regexp: 'port: (\d{5})-\d{0,4}(\d{5})'
replace: 'port: \1-\2'
- name: Customer {{ customer_id }} - Load the properly formatted ip addresses and ports
hosts: localhost:all-n7k:srx-clu:all-mx80:all-vsrx
gather_facts: no
connection: local
tags: [format_vars]
tasks:
- include_vars:
file: "{{ formatted_myvar_file }}"
ignore_errors: yes

Dictionary inside Dictionary in Ansible

I have the following Ansible playbook file:
1 ---
2 - name: Provision cluster
3 connection: local
4 hosts: localhost
5 gather_facts: False
6 vars_files:
7 - ../vars_files/credentials/foo.yml
8 - ../vars_files/credentials/bar.yml
9
10 vars:
11 - ec2_hosts:
12 - node1:
13 - address: 1.1.1.1
14 - node2:
15 - address: 1.1.1.2
16 - node3:
17 - address: 1.1.1.3
18 - node4:
19 - address: 1.1.1.4
What I'm trying to do is imply to have a dictionary inside another dictionary. On the second I'm going to declare a dictionary between hostname and its IP address. However I'm getting the following error:
ERROR: Syntax Error while loading YAML script, provision.yml
Note: The error may actually appear before this position: line 10, column 1
vars:
I personally prefer to use JSON (or Python'ish) expressions when defining complex variables as I find those more readable compared to complex vars defined in YAML syntax. E.g.:
vars:
ec2_hosts: {
node1: {
address: 1.1.1.1,
hostname: ec2_node1
},
node2: {
address: 1.1.1.2,
hostname: ec2_node2
},
},
If you must use YAML, hope that's actually the structure you want, it's different than the previous one:
vars:
- ec2_hosts:
- host1:
- address: 1.1.1.1
- host2:
- address: 1.1.1.2
Here is the tested code in case there are some other syntax errors:
- name: Provision cluster
connection: local
hosts: localhost
gather_facts: False
# vars_files:
# - ../vars_files/credentials/foo.yml
# - ../vars_files/credentials/bar.yml
vars:
- ec2_hosts:
- host1:
- address: 1.1.1.1
- host2:
- address: 1.1.1.2
tasks:
- debug: msg="{{ec2_hosts}}"
PS: I copied LOTS of trailing whitespaces from your post, hope they don't exist in your actual playbook YAML.

Resources