Sharing the same host var in ansible inventory - ansible

I'm looking for a correct way to construct an inventory to share the same var.
Here is my inventory
{
"groupA": {
"hosts": [
"192.168.1.1"
]
},
"groupB": {
"hosts": [
"192.168.1.2"
]
},
"vars": {
"ansible_ssh_user": "admin",
"ansible_ssh_private_key_file": "/admin.pem",
"ansible_become": "yes",
"ansible_become_method": "sudo"
}
}
I want both groupA and groupB to use the same var declared.
Moreover, how can I specify in playbook to run both groupA and groupB. The following one seems not to work
hosts: groupA, groupB
[UPDATE] Below is the correct construct after getting support from Konstantin Suvorov.
{
"groupA": {
"hosts": [
"192.168.1.1"
]
},
"groupB":{
"hosts":[
"192.168.1.2"
]
},
"root":{
"children":[
"groupA",
"groupB"
],
"vars": {
"ansible_ssh_user": "admin"
}
}
}

Drop your vars into some dummy group that is parent to both groups:
"root": {
"children": ["groupA", "groupB"],
"vars": {
"ansible_ssh_user": "admin"
}
},
Correct pattern is hosts: groupA:groupB or hosts: group[AB]

Related

Ansible search and query

Updated with suggestions from larsks.
With the following structure
"intf_output_ios": {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python3"
},
"changed": false,
"failed": false,
"gathered": [
{
"name": "GigabitEthernet0/0"
},
{
"mode": "trunk",
"name": "GigabitEthernet0/1",
"trunk": {
"allowed_vlans": [
"10",
"20",
"30",
"99",
"100"
],
"encapsulation": "dot1q"
}
},
{
"mode": "trunk",
"name": "GigabitEthernet0/2",
"trunk": {
"allowed_vlans": [
"10",
"20",
"30",
"99",
"100"
],
"encapsulation": "dot1q"
}
},
{
"access": {
"vlan": 30
},
"mode": "access",
"name": "GigabitEthernet0/3"
},
{
"name": "GigabitEthernet1/0"
},
{
"name": "GigabitEthernet1/1"
},
{
"name": "GigabitEthernet1/2"
},
{
"name": "GigabitEthernet1/3"
},
{
"name": "GigabitEthernet2/0"
},
{
"name": "GigabitEthernet2/1"
},
{
"name": "GigabitEthernet2/2"
},
{
"name": "GigabitEthernet2/3"
},
{
"name": "GigabitEthernet3/0"
},
{
"name": "GigabitEthernet3/1"
},
{
"name": "GigabitEthernet3/2"
},
{
"access": {
"vlan": 99
},
"mode": "access",
"name": "GigabitEthernet3/3"
}
]
}
To print only the ports in VLAN 30 use the following?
- name: "P901T6: Set fact to include only access ports - IOS"
set_fact:
access_ports_ios_2: "{{ intf_output_ios | json_query(query) }}"
vars:
query: >-
gathered[?access.vlan==`30`]
- name: "P901T7: Dump list of access ports - IOS"
debug:
var=access_ports_ios_2
NOTE: It is important to use 30 (with backticks) and not '30'
I have gone through https://docs.ansible.com/ansible/latest/user_guide/playbooks_filters.html#managing-list-variables without really understanding how to fix this. If someone has some good link that would be very useful
With a structure like
ok: [access01] => {
"access_ports_ios": [
{
"access": {
"vlan": 30
},
"mode": "access",
"name": "GigabitEthernet0/3"
},
{
"access": {
"vlan": 99
},
"mode": "access",
"name": "GigabitEthernet3/3"
}
]
}
To get ports in vlan 30 use:
- debug:
var: access_ports_ios|json_query(query)
vars:
query: >-
[?access.vlan==`30`]
Note:
If you want to use a variable for vlan instead of hard-coding it. I had to do as follows:
- name: Debug 4
debug:
var: access_ports_ios|json_query('[?access.vlan==`{{ src_vlan | int}}`]')
You're asking for gathered.access, but gathered is a list and does not have an access attribute. You want "all items from gathered for which access.vlan is 30 (and note that the value of access.vlan is an integer, not a string):
- debug:
var: intf_output_ios|json_query(query)
vars:
query: >-
gathered[?access.vlan==`30`]
Which given you example input produces:
TASK [debug] *******************************************************************
ok: [localhost] => {
"intf_output_ios|json_query(query)": [
{
"access": {
"vlan": 30
},
"mode": "access",
"name": "GigabitEthernet0/3"
}
]
}
I'm going to reiterate advice I often give for json_query questions: use something like jpterm or the JMESPath website to test JMESPath expressions against your actual data. This makes it much easier to figure out where an expression might be going wrong.

join multiple arrays in complex data structure with jmespath

I'm trying to transform NFS exports, described in complex data structure, to config option accepted by nfs-server daemon which later be used in ansible.
I have:
nfs_exports:
- path: /export/home
state: present
options:
- clients: "192.168.0.0/24"
permissions:
- "rw"
- "sync"
- "no_root_squash"
- "fsid=0"
- path: /export/public
state: present
options:
- clients: "192.168.0.0/24"
permissions:
- "rw"
- "sync"
- "root_squash"
- "fsid=0"
- clients: "*"
permissions:
- "ro"
- "async"
- "all_squash"
- "fsid=1"
which must become:
[
{
"options": "192.168.0.0/24(rw,sync,no_root_squash,fsid=0)",
"path": "/export/home",
"state": "present"
},
{
"options": "192.168.0.0/24(rw,sync,root_squash,fsid=0) *(ro,async,all_squash,fsid=1)",
"path": "/export/public",
"state": "present"
}
]
So far I was able, using {{ nfs_exports | json_query(query) }}
query: "[].{path:path,state:state,options:options.join(` `,[].join(``,[clients,`(`,join(`,`,permissions),`)`]))}"
get
{
"options": "192.168.0.0/24(rw,sync,no_root_squash,fsid=0)",
"path": "/export/home",
"state": "present"
},
{
"options": "192.168.0.0/24(rw,sync,root_squash,fsid=0)*(ro,async,all_squash,fsid=1)",
"path": "/export/public",
"state": "present"
}
It's probably simple but I can't get pass that last options join, space ' ' gets removed.
So if someone knows the correct query additional explanation will be much appreciated.
Given the query:
[].{ path: path, state: state, options: join(' ', options[].join('', [clients, '(', join(',', permissions), ')'])) }
On the JSON
{
"nfs_exports": [
{
"path": "/export/home",
"state": "present",
"options": [
{
"clients": "192.168.0.0/24",
"permissions": [
"rw",
"sync",
"no_root_squash",
"fsid=0"
]
}
]
},
{
"path": "/export/public",
"state": "present",
"options": [
{
"clients": "192.168.0.0/24",
"permissions": [
"rw",
"sync",
"root_squash",
"fsid=0"
]
},
{
"clients": "*",
"permissions": [
"ro",
"async",
"all_squash",
"fsid=1"
]
}
]
}
]
}
It would give you your expected output:
[
{
"path": "/export/home",
"state": "present",
"options": "192.168.0.0/24(rw,sync,no_root_squash,fsid=0)"
},
{
"path": "/export/public",
"state": "present",
"options": "192.168.0.0/24(rw,sync,root_squash,fsid=0) *(ro,async,all_squash,fsid=1)"
}
]
Please mind: the string litteral `` wont work on a space character string, because, as pointed in the documentation, it will be parsed as JSON:
A literal expression is an expression that allows arbitrary JSON objects to be specified
Source: https://jmespath.org/specification.html#literal-expressions
This is quite easy when you get to the point of:
[].{ path: path, state: state, options: options[].join('', [clients, '(', join(',', permissions), ')']) }
Which is something you seems to have achived, that gives
[
{
"path": "/export/home",
"state": "present",
"options": [
"192.168.0.0/24(rw,sync,no_root_squash,fsid=0)"
]
},
{
"path": "/export/public",
"state": "present",
"options": [
"192.168.0.0/24(rw,sync,root_squash,fsid=0)",
"*(ro,async,all_squash,fsid=1)"
]
}
]
Because you are just left with joining the whole array in options with a space as glue character.

Dynamic inventory groups from ansible plugin: nmap

I'm trying to use the nmap plugin in ansible to create a dynamic inventory, and then group things that the plugin returns. Unfortunately, I'm missing something, because I can't seem to get a group to be created.
In this scenario, I have a couple hosts named unknownxxxxxxxx that I would like to group.
plugin: nmap
strict: false
address: 10.0.1.0/24
ports: no
groups:
unknown: "'unknown' in hostname"
I run my plugin -
ansible-inventory -i nmap.yml --export --output=inv --list
but the return is always the same...
By now, I've resorted to guessing possible var names
host, hosts, hostnames, hostname, inventory_hostname, hostvars, host.fqdn, and the list goes on and on...
I'm obviously missing something basic, but I can't seem to find anything via search that has yielded any results.
Can someone help me understand what I'm doing wrong with jinja?
Perhaps I need to use compose: and keyed_groups: ?
I'm obviously missing something basic...
I'm not sure that you are. I agree that according to the documentation the nmap plugin is supposed to work the way you're trying to use it, but like you I'm not able to get the groups or compose keys to work as described.
Fortunately, we can work around that problem by directly using the constructed inventory plugin.
We'll need to use an inventory directory, rather than an inventory file, since we need multiple inventory files. We'll put the following into our ansible.cfg:
[defaults]
inventory = inventory
And then we'll create a directory inventory, into which we'll place two files. First, we'll put your nmap inventory in inventory/10nmap.yml. It will look like this:
plugin: nmap
strict: false
address: 10.0.1.0/24
ports: false
And then we'll put the configuration for the constructed plugin to inventory/20constructed.yml:
plugin: constructed
strict: False
groups:
unknown: "'unknown' in inventory_hostname"
We've named the file 10nmap.yml and 20constructed.yml because we need to ensure that the constructed plugin runs after the nmap plugin (also, we're checking against inventory_hostname here because that's the canonical name of a host in your Ansible inventory).
With all this in place, you should see the behavior you're looking for: hosts with unknown in the inventory_hostname variable will end up in the unknown group.
I believe that groups apply only to attributes that the plugin returns, so you should be looking at its output.
For example, running ansible-inventory -i nmap-inventory.yml --list with
---
plugin: nmap
address: 192.168.122.0/24
strict: false
ipv4: yes
ports: yes
sudo: true
groups:
without_hostname: "'192.168.122' in name"
with_ssh: "ports | selectattr('service', 'equalto', 'ssh')"
produces
{
"_meta": {
"hostvars": {
"192.168.122.102": {
"ip": "192.168.122.102",
"name": "192.168.122.102",
"ports": [
{
"port": "22",
"protocol": "tcp",
"service": "ssh",
"state": "open"
}
]
},
"192.168.122.204": {
"ip": "192.168.122.204",
"name": "192.168.122.204",
"ports": [
{
"port": "22",
"protocol": "tcp",
"service": "ssh",
"state": "open"
},
{
"port": "8080",
"protocol": "tcp",
"service": "http",
"state": "open"
}
]
},
"fedora": {
"ip": "192.168.122.1",
"name": "fedora",
"ports": [
{
"port": "53",
"protocol": "tcp",
"service": "domain",
"state": "open"
},
{
"port": "6000",
"protocol": "tcp",
"service": "X11",
"state": "open"
}
]
}
}
},
"all": {
"children": [
"ungrouped",
"with_ssh",
"without_hostname"
]
},
"ungrouped": {
"hosts": [
"fedora"
]
},
"with_ssh": {
"hosts": [
"192.168.122.102",
"192.168.122.204"
]
},
"without_hostname": {
"hosts": [
"192.168.122.102",
"192.168.122.204"
]
}
}
As you can see, I'm using name and ports because the entries have these attributes. I could've also used ip.
To further clarify the point, when I run the plugin with ports: no, the with_ssh grouping filter doesn't produce anything because there are no ports in the output.
{
"_meta": {
"hostvars": {
"192.168.122.102": {
"ip": "192.168.122.102",
"name": "192.168.122.102"
},
"192.168.122.204": {
"ip": "192.168.122.204",
"name": "192.168.122.204"
},
"fedora": {
"ip": "192.168.122.1",
"name": "fedora"
}
}
},
"all": {
"children": [
"ungrouped",
"without_hostname"
]
},
"ungrouped": {
"hosts": [
"fedora"
]
},
"without_hostname": {
"hosts": [
"192.168.122.102",
"192.168.122.204"
]
}
}

Ansible: How to modify a list of dicts

I want to modify dicts in a list within hostvars with a new entry for the IP address I get from the IPAM.
{
"vm_guest_networks": [
{
"device_type": "vmxnet3",
"state": "present",
"subnet": "10.91.1.0/24"
},
{
"device_type": "vmxnet3",
"state": "present",
"subnet": "10.91.0.0/24"
}
]
}
Within a loop I have the subnet to identify the right dict and the IP address I want to add with the ipv4_address key so the result should look like:
{
"vm_guest_networks": [
{
"device_type": "vmxnet3",
"state": "present",
"subnet": "10.91.1.0/24",
"ipv4_address": "10.91.1.216"
},
{
"device_type": "vmxnet3",
"state": "present",
"subnet": "10.91.0.0/24",
"ipv4_address": "10.91.0.21"
}
]
}
The current WIP ansible code is at https://pastebin.com/bFc1Ww2K
Let's assume the list of IPs is available. For example
ip4: [10.91.1.216, 10.91.0.21]
Let's use combine filter and Extended loop variables to create a new list where each dictionary will be updated. For example
- set_fact:
mydata: "{{ mydata|default([]) +
[item|combine({'ipv4_address': ip4[ansible_loop.index0]})] }}"
loop: "{{ vm_guest_networks }}"
loop_control:
extended: yes
- set_fact:
vm_guest_networks: "{{ mydata }}"
- debug:
var: vm_guest_networks
give
"vm_guest_networks": [
{
"device_type": "vmxnet3",
"ipv4_address": "10.91.1.216",
"state": "present",
"subnet": "10.91.1.0/24"
},
{
"device_type": "vmxnet3",
"ipv4_address": "10.91.0.21",
"state": "present",
"subnet": "10.91.0.0/24"
}
]

update user dict with list of values

Ansible 2.7, server and targets are running ubuntu.
At a point of my playbook, I have a users list of dicts:
ok: [virtual_tournesol] => {
"users": [
{
"key": "toto",
"value": [
"toto",
"/home/toto/.ssh/id_rsa"
]
},
{
"key": "riri",
"value": [
"www-data",
"/home/riri/.ssh/id_rsa"
]
}
]
}
I also fetch generated ssh keys within a pubkeys list:
ok: [virtual_tournesol] => {
"pubkeys": [
"ssh-rsa XXX1 ansible-generated on tournesol",
"ssh-rsa XXX2 ansible-generated on tournesol"
]
}
How could I merge both data such I can continue my tasks using users only ? I wish to get:
ok: [virtual_tournesol] => {
"users": [
{
"key": "toto",
"value": [
"toto",
"/home/toto/.ssh/id_rsa",
"ssh-rsa XXX1 ansible-generated on tournesol"
]
},
{
"key": "riri",
"value": [
"www-data",
"/home/riri/.ssh/id_rsa",
"ssh-rsa XXX2 ansible-generated on tournesol"
]
}
]
}
EDIT: In python, that would give something like:
for u, k in zip(users, pubkeys):
u['value'].append(k)
zip is a good choice. The code below
- set_fact:
user1: "{{ user1|default({}) | combine(
{ item.0.key: item.0.value + [ item.1 ] } ) }}"
loop: "{{ users|zip(pubkeys)|list }}"
- debug:
msg: "{{ user1|dict2items }}"
gives
"msg": [
{
"key": "riri",
"value": [
"www-data",
"/home/riri/.ssh/id_rsa",
"ssh-rsa XXX2 ansible-generated on tournesol"
]
},
{
"key": "toto",
"value": [
"toto",
"/home/toto/.ssh/id_rsa",
"ssh-rsa XXX1 ansible-generated on tournesol"
]
}
]

Resources