Considering the output of ios_facts module (or any similar gathering fact module), I wrote the following Ansible playbook:
name: checking interface status
gather_facts: no
hosts: iosxe
tasks:
name: get cisco config
cisco.ios.ios_facts:
gather_subset: all
register: cisco_output
name: task001
debug:
var: cisco_output['ansible_facts']['ansible_net_interfaces']
loop: "{{ ansible_facts['ansible_net_interfaces'] | dict2items }}"
when: item.lineprotocol == "up"
For the sake of practicing Ansible, I wanted the playbook to show list of interfaces in "up" state. The relative part in the output of the "ios_fact" shows that it is a "dictionary" not "list".
"ansible_net_interfaces": {
"GigabitEthernet1": {
"bandwidth": 1000000,
"description": null,
"duplex": "Full",
"ipv4": [
{
"address": "10.106.31.229",
"subnet": "24"
}
],
"lineprotocol": "up",
"macaddress": "000c.29c3.a8f3",
"mediatype": "Virtual",
"mtu": 1500,
"operstatus": "up",
"type": "CSR vNIC"
},
"GigabitEthernet2": {
"bandwidth": 1000000,
"description": "CSR2",
"duplex": "Full",
"ipv4": [
{
"address": "10.171.73.33",
"subnet": "27"
}
],
"lineprotocol": "up",
"macaddress": "000c.29c3.a8fd",
"mediatype": "Virtual",
"mtu": 1500,
"operstatus": "up",
"type": "CSR vNIC"
}, ...
I got several different errors and tried to workaround by changing the playbook each time as these:
loop: "{{ ansible_facts['ansible_net_interfaces'] | dict2items }}"
loop: "{{ cisco_output['ansible_facts']['ansible_net_interfaces'] }}"
loop: "{{ cisco_output['ansible_facts']['ansible_net_interfaces'] | dict2items }}"
But was unsuccessful as I got different errors again. What would be the right way of this?
The dict2items filter will convert the dict to an array of items, where the keys will be GigabitEthernet1, GigabitEthernet2, and the values of these keys will sub-dicts.
"item.key": "GigabitEthernet1"
"item.key": "GigabitEthernet2"
So, the lineprotocol key will be in the item.value, i.e. item.value.lineprotocol. A simple task such as below can demonstrate the same:
- debug:
msg: "{{ item.key }} is up"
loop: "{{ cisco_output['ansible_facts']['ansible_net_interfaces | dict2items }}"
when: item.value.lineprotocol == "up"
The attribute ipv4 is a list. Use with_subelements if you want to take into account the fact that there might be more IP addresses configured, e.g.
- debug:
msg: "{{ item.0.key }} {{ item.1.address }}"
with_subelements:
- "{{ ansible_net_interfaces|dict2items|
selectattr('value.lineprotocol', 'eq', 'up') }}"
- value.ipv4
gives
msg: GigabitEthernet1 10.106.31.229
msg: GigabitEthernet2 10.171.73.33
Related
I'm getting a bit lost with next variable types and am hoping for some direction in a specific task please:
The Goal:
Based on a list of username:publickey values. I'd like to:
ensure the user exists on the target system
if the user does exist then:
- ensure the "/home/$user/.ssh/authorized_keys" file exists with the correct permissions through the path.
The Scene:
I have a variable:
ssh_vars:
auth_keys:
bob: "bobs_public_key_string"
anne: "annes_public_key_string"
anon: "anons_public_key_string
I need to iterate over this variable and for each auth_keys item call a tasklist:
- name: loop through the auth_keys and call ssh_dirs.yml for each
ansible.builtin.include_tasks: "ssh_dirs.yaml"
loop: "{{ ssh_vars.auth_keys }}"
However, I only really want to do this when the auth_key(key) is a user which already exists on the host.
I have been playing with getent, within "ssh_dirs.yaml":
- name: "Ensure the user exists on the target system"
ansible.builtin.getent:
database: passwd
key: "{{ item.name }}"
fail_key: false
register: userlookup
which creates what i think is a list of dictionaries:
ok: [ans-client.local] => {
"userlookup": {
"changed": false,
"msg": "All items completed",
"results": [
{
"ansible_facts": {
"getent_passwd": {
"bob": [
"x",
"1003",
"1003",
"",
"/home/bob",
"/usr/bin/bash"
]
}
},
"ansible_loop_var": "item",
"changed": false,
"failed": false,
"invocation": {
"module_args": {
"database": "passwd",
"fail_key": false,
"key": "bob",
"service": null,
"split": null
}
},
"item": {
"key": "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDvIZuaBhAIGShw21rkvgqyvNePunbVs6OtOBhYJOY2P anne#ans-server",
"name": "bob"
}
},
{
"ansible_facts": {
"getent_passwd": {
"anne": [
"x",
"1000",
"1000",
"anne",
"/home/anne",
"/bin/bash"
]
}
},
"ansible_loop_var": "item",
"changed": false,
"failed": false,
"invocation": {
"module_args": {
"database": "passwd",
"fail_key": false,
"key": "anne",
"service": null,
"split": null
}
},
"item": {
"key": "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKr/76O3hLJlcyZuy7EJxf7sC1z9BSHMuxGsFGBibJY3 anne#ans-server",
"name": "anne"
}
},
{
"ansible_facts": {
"getent_passwd": {
"anon": null
}
},
"ansible_loop_var": "item",
"changed": false,
"failed": false,
"invocation": {
"module_args": {
"database": "passwd",
"fail_key": false,
"key": "anon",
"service": null,
"split": null
}
},
"item": {
"key": "SOMEKEY",
"name": "anon"
},
"msg": "One or more supplied key could not be found in the database."
}
],
"skipped": false
}
}
But I can't figure out how to isolate this list to ensure the include_tasks: is not called if the user doesn't exist.
- name: loop through the auth_keys and call ssh_dirs.yml for each
ansible.builtin.include_tasks: "ssh_dirs.yaml"
loop: "{{ ssh_vars.auth_keys }}"
when: userlookup.results.???????
How can I figure out how to reference this nested variable, and how best to isolate a non-missing user?
Something like userlookup.results.msg is not defined might work but it's very loose - is there something better I'm missing?
See registering variables with loop
The global idea is
loop over you var to get the existing/unavailable users
loop over the results of that previous task for you next one. The original loop variable is available in the item key of each result and you can filter as you like.
For your particular case, in a nutshell (untested):
- name: Ensure the user exists on the target system
ansible.builtin.getent:
database: passwd
key: "{{ item.name }}"
register: userlookup
ignore_errors: true
loop: "{{ ssh_vars.auth_keys }}"
- name: Call ssh_dirs.yml for each existing users
ansible.builtin.include_tasks: ssh_dirs.yaml
loop: "{{ userlookup.results | select('success') }}"
loop_control:
loop_var: user_checked
vars:
userloop: "{{ user_checked.item }}"
I think I've solved it, although maybe there's a better thing to look for in the getent response than just the msg?
The logic and variable reference which works:
- name: "Ensure the user exists on the target system"
ansible.builtin.getent:
database: passwd
key: "{{ item.name }}"
fail_key: false
register: userlookup
loop: "{{ ssh_vars.auth_keys }}"
- name: Build a list of usernames which don't exist on the remote host (missing_users)
ansible.builtin.set_fact:
missing_users: "{{ missing_users | default([]) + [usercheck.item.name | string] }}"
loop: "{{ userlookup.results }}"
loop_control:
loop_var: usercheck
when: usercheck.msg is defined
- name: loop through the users and ensure the necessary user folders and files are there
ansible.builtin.include_tasks: "ssh_dirs.yaml"
loop: "{{ ssh_vars.auth_keys }}"
loop_control:
loop_var: userloop
when: userloop.name not in missing_users
Although this is still checking the msg: output mindlessly so only a partial solution
remote_address_phase2:
- 192.168.88.0/24
- 192.168.1.0/24
task:
i have to create a firewall group
- name: "addrgrp"
fortios_firewall_addrgrp:
vdom: "{{ vdom }}"
state: "present"
firewall_addrgrp:
allow_routing: "disable"
#category: "default"
color: "21"
comment: "try"
exclude: "disable"
fabric_object: "disable"
member:
- name: "NET-{{ item}}"
name: "try"
type: "default"
with_items: "{{ remote_address_phase2 }}"
if i made this activity i have 2 different task but the last operation overwrite the first
any idea?
Going by the example in the documentation for the module, it seems that the member: parameter takes a list of dicts.
Example from module documentation:
member:
-
name: "default_name_7 (source firewall.address.name firewall.addrgrp.name)"
Haven't tested it, but we can create a similar structure before "addrgrp" task with set_fact and use the newly created variable.
- set_fact:
fw_members: "{{ fw_members | default([]) + [{'name': 'NET-' ~ item}] }}"
loop: "{{ remote_address_phase2 }}"
This gives:
"fw_members": [
{
"name": "NET-192.168.88.0/24"
},
{
"name": "NET-192.168.1.0/24"
}
]
It should then be possible to pass this variable as a value to the member: parameter. Example:
- set_fact:
fw_members: "{{ fw_members | default([]) + [{'name': 'NET-' ~ item}] }}"
loop: "{{ remote_address_phase2 }}"
- name: "addrgrp"
fortios_firewall_addrgrp:
vdom: "{{ vdom }}"
state: "present"
firewall_addrgrp:
allow_routing: "disable"
#category: "default"
color: "21"
comment: "try"
exclude: "disable"
fabric_object: "disable"
member: "{{ fw_members }}"
name: "try"
type: "default"
I'm using the Cisco ACI_REST module. I'm building a playbook to shutdown all of my ACI EPGs. The ACI_EPG module is used to create the dict.
- name: Get List of EPGs
cisco.aci.aci_epg:
host: "{{ inventory_hostname }}"
username: "{{ ansible_user }}"
password: "{{ ansible_password }}"
validate_certs: no
state: query
delegate_to: localhost
register: epg_list
It creates a dictionary that looks like this:
"item": {
"key": "current",
"value": [
{
"fvAEPg": {
"attributes": {
"dn": "uni/tn-PC/ap-PROD_AP/epg-VLAN80_EPG",
"shutdown": "no",
},
"children": [
{
"fvRsBd": {
"attributes": {
"annotation": "",
"tDn": "uni/tn-PC/BD-VLAN80_BD",
"tRn": "BD-VLAN80_BD",
"tType": "name",
"tnFvBDName": "VLAN80_BD",
"uid": "0"
},
"children": [
{
"fvSubnetBDDefCont": {
"attributes": {
"name": "",
"nameAlias": "",
"status": ""
}
}
}
]
}
}
]
}
}
I need to loop through the DN values.
"dn": "uni/tn-PC/ap-PROD_AP/epg-VLAN80_EPG"
I'm playing with dict2items but am stuck. How to I reference the nested items in my dictionary? Here is some test code that I'm working with.
- name: Display EPG List
ansible.builtin.debug:
msg: "{{ item.key }} - {{ item.value }}"
loop: "{{ epg_list | dict2items }}"
Thanks,
Q: "Loop through the DN values."
A: Try json_query, e.g.
- debug:
msg: "{{ item }}"
loop: "{{ epg_list.item.value|
json_query('[].*.attributes.dn')|
flatten }}"
gives
msg: uni/tn-PC/ap-PROD_AP/epg-VLAN80_EPG
Given the data
epg_list:
item:
key: current
value:
- fvAEPg:
attributes:
dn: uni/tn-PC/ap-PROD_AP/epg-VLAN80_EPG
shutdown: 'no'
children:
- fvRsBd:
attributes:
annotation: ''
tDn: uni/tn-PC/BD-VLAN80_BD
tRn: BD-VLAN80_BD
tType: name
tnFvBDName: VLAN80_BD
uid: '0'
children:
- fvSubnetBDDefCont:
attributes:
name: ''
nameAlias: ''
status: ''
If the data is
epg_list:
current:
- fvAEPg:
attributes:
dn: uni/tn-PC/ap-PROD_AP/epg-VLAN80_EPG
shutdown: 'no'
children:
- fvRsBd:
attributes:
annotation: ''
tDn: uni/tn-PC/BD-VLAN80_BD
tRn: BD-VLAN80_BD
tType: name
tnFvBDName: VLAN80_BD
uid: '0'
children:
- fvSubnetBDDefCont:
attributes:
name: ''
nameAlias: ''
status: ''
the task below gives the same result
- debug:
msg: "{{ item }}"
loop: "{{ epg_list.current|
json_query('[].*.attributes.dn')|
flatten }}"
I'm struggling to loop over hostvars of the registered output of all the hosts in dynamic inventory.
Here is the code.
$cat collect.yaml
---
- hosts: "{{ env }}"
become: True
tasks:
- name: Get dockerinfo
docker_host_info:
containers: yes
register: result
- name: Debug dockerInfo
debug:
var: result.containers
- name: dynamic grouping
add_host:
name: "{{ item[0] }}"
groups: "{{ item[1].Image | regex_replace('.*?/(.*?):.*', '\\1') }}"
loops:
- "{{ ansible_play_batch }}"
- "{{ hostvars[item].result.containers }}"
Error i get is item not defined. I would want the hosts refer to their respective result.containers. Not sure on how to use hostvars for the host to refer their respective result.containers.
Here is result.container output.
TASK [Debug dockerInfo]
ok: [vm1.nodekite.com] => {
"result.containers": [
{
"Image": "ca.docker/webproxy:1.0.0",
},
{
"Image": "docker.local/egacustomer:1.0.1",
},
]}
ok: [vm2.nodekite.com ] => {
"result.containers": [
{
"Image": "ca.docker/webproxyui:1.0.0",
},
{
"Image": "cna-docker-local/lega-customer:1.0.1",
},
]}
Here is the what i'm trying to achieve
changed: [vm1.nodekite.com] => {
"add_host": {
"groups": [
"webproxy"
],
"host_name": "vm1.nodekite.com",
},
changed: [vm1.nodekite.com] => {
"add_host": {
"groups": [
"egacustomer"
],
"host_name": "vm1.nodekite.com",
},
changed: [vm2.nodekite.com] => {
"add_host": {
"groups": [
"webproxy" >> this should be webproxyui
],
"host_name": "vm2.nodekite.com",
},
changed: [vm2.nodekite.com] => {
"add_host": {
"groups": [
"egacustomer" >> this should be lega-customer
],
"host_name": "vm2.nodekite.com",
},
Any help would be greatly appreciated.
I would run this task using Images Names
- hosts: "{{ group }}"
gather_facts: false
become: true
become_method: sudo
tasks:
- name: stop or restart docker containers
command: "docker {{ state }} {{ container_name }}"
How about just group_by paired with the loop?
- hosts: "{{ env }}"
become: True
tasks:
- name: Get dockerinfo
docker_host_info:
containers: yes
register: result
- debug:
var: result.containers
- group_by:
key: "container_{{ item.image | regex_replace('.*?/(.*?):.*', '\\1') }}"
loop: "{{ result.containers }}"
- debug:
var: group_names
You don't need to add the prefix. But this would add each host to groups with their container image prefixes. You should be able to use the group later in the play or playbook.
I am struggling to loop over registered results.containers for all the hosts in the dynamic inventory.
Here is the code.
$cat collect.yaml
---
- hosts: "{{ env }}"
become: True
tasks:
- name: Get dockerinfo
docker_host_info:
containers: yes
register: result
- name: Debug dockerInfo
debug:
var: result.containers
- name: dynamic grouping
add_host:
name: "{{ item[0] }}"
groups: "{{ item[1].Image | regex_replace('.*?/(.*?):.*', '\\1') }}"
container_name: '{{ item[1].Names[0] | regex_replace("^/", "") }}'
with_nested:
- "{{ ansible_play_batch }}"
- "{{ result.containers }}"
Here is result.containers output.
TASK [Debug dockerInfo]
ok: [vm1.nodekite.com] => {
"result.containers": [
{
"Image": "ca.docker/webproxy:1.0.0",
"Names": [
"/customer1"
],
},
{
"Image": "docker.local/egacustomer:1.0.1",
"Names": [
"/webproxy"
],
},
]}
ok: [vm2.nodekite.com ] => {
"result.containers": [
{
"Image": "ca.docker/webproxy:1.0.0",
"Names": [
"/webproxyui"
],
},
{
"Image": "cna-docker-local/lega-customer:1.0.1",
"Names": [
"/webproxy"
],
},
]}
ok: [vm3.nodekite.com ] => {
"result.containers": [
{
"Image": "ca.docker/webproxy:1.0.0",
"Names": [
"/webproxy"
],
},
{
"Image": "local.docker/saga-customer:1.0.1",
"Names": [
"/customerr
],
},
]}
Right now item[1].Image and item[1].Names[0] is only taken from first host's(vm1.nodekite.com) results.containers output. I would like to loop over for every hosts. So that, I could create dynamic group for all the hosts with their respective containers. With my code, hosts vm1,vm2,vm3 all are referring to vm1.nodekite.com's result.containers but i want the hosts to refer to their respective containers. Any help would be greatly appreciated.
I have update dynamic grouping task ouput for clarification.
changed: [vm1.nodekite.com] => {
"add_host": {
"groups": [
"webproxy"
],
"host_name": "vm1.nodekite.com",
"host_vars": {
"container_name": "customer1" }
},
changed: [vm1.nodekite.com] => {
"add_host": {
"groups": [
"egacustomer"
],
"host_name": "vm1.nodekite.com",
"host_vars": {
"container_name": "webproxy" }
},
changed: [vm2.nodekite.com] => {
"add_host": {
"groups": [
"webproxy" >> this should be webproxy
],
"host_name": "vm2.nodekite.com",
"host_vars": {
"container_name": "customer1" } >> this should be webproxyui
},
changed: [vm2.nodekite.com] => {
"add_host": {
"groups": [
"egacustomer" >> this should be lega-customer
],
"host_name": "vm2.nodekite.com",
"host_vars": {
"container_name": "webproxy" } >> this should be webproxy
},
if you see vm2 is still referring to vm1's result.containers output.
when i try this...i get item not defined error.
- name: adding it to groups using images
add_host:
name: "{{ item[0] }}"
groups: "{{ item[1].Image | regex_replace('.*?/(.*?):.*', '\\1') }}"
container_name: '{{ item[1].Names[0] | regex_replace("^/", "") }}'
loop:
- "{{ ansible_play_batch }}"
- "{{ myresult.containers }}"
vars:
myresult: "{{ hostvars[item].result }}"
run_once: true
Q: "Hosts shall refer to their respective containers."
A: Use hostvars. For example
- name: dynamic grouping
debug:
msg:
- "name: {{ item }}"
- "groups: {{ my_result.containers|
map(attribute='Image')|
map('regex_replace', '.*?/(.*?):.*', '\\1')|
list }}"
- "container_names: {{ my_result.containers|
map(attribute='Names')|
map('regex_replace', '\/', '')|
list }}"
loop: "{{ ansible_play_batch }}"
vars:
my_result: "{{ hostvars[item].result }}"
run_once: true
gives
ok: [vm1.nodekite.com] => (item=vm1.nodekite.com) =>
msg:
- 'name: vm1.nodekite.com'
- 'groups: [''webproxy'', ''egacustomer'']'
- 'container_names: ["[''customer1'']", "[''webproxy'']"]'
ok: [vm1.nodekite.com] => (item=vm2.nodekite.com) =>
msg:
- 'name: vm2.nodekite.com'
- 'groups: [''webproxy'', ''lega-customer'']'
- 'container_names: ["[''webproxyui'']", "[''webproxy'']"]'
ok: [vm1.nodekite.com] => (item=vm3.nodekite.com) =>
msg:
- 'name: vm3.nodekite.com'
- 'groups: [''webproxy'', ''saga-customer'']'
- 'container_names: ["[''webproxy'']", "[''customer'']"]'
(Feel free to fit the code to your needs.)
I was having an issue of getting the item passed into the name below to be a plain string of: item='nginx' and not item='[u'/nginx]'
To get around this, I did the following:
- name: Get docker containers
become: docker
community.docker.docker_container
containers: yes
register: docker_info
- name: Stop running containers
become: docker
community.docker.docker_container
name: "{{ item }}"
state: stopped
loop: "{{ docker_info.containers | sum(attribute='Names', start=[]) | map('regex_replace','\\/','') | list }}"
when: item in apps.split(,)
In this case the apps is a comma deliminated string variable I passed into the ansible playbook to limit which apps to stop.
The sum piece, flattens the Names of all the apps running into a single list.
The regex piece removes the / in the Names parameter