My goal is to create new devices in NetBox with ansible. So, I use netbox_device for this reason. I am getting the required information (device names, types, serial numbers etc) from a json file like the following one:
{
"results":{
"infos": [
{
"device_type": "type1",
"device_name": "name1",
"serial_number": "num1"
},
{
"device_type": "type2",
"device_name": "name2",
"serial_number": "num2"
}
]
}
}
So, what I am doing is that I have this simple task to create 1 new device :
- name: Create new devices within Netbox
netbox_device:
netbox_url: http://url.goes.here
netbox_token: 7575747448..66353
data:
name: name1
device_type: type1
device_role: Core Switch
serial_number: "num1"
site: Main
state: present
This is something simple if someone wants to create one specific new device, but I want to dynamically create new devices while looping some lists with the required info.
In particular, I create from the json file one list with all devices names
ok: [localhost] => {
"ansible_facts": {
"dev_names": [
"nameisone",
"nameistwo"
]
},
"changed": false
}
Then I create another list from the json file with the device types
ok: [localhost] => {
"ansible_facts": {
"dev_types": [
"type_one",
"type_two"
]
},
"changed": false
}
So now I have a task with 1 loop regarding the above lists:
- name: Create new devices within Netbox looping the lists with the required info
netbox_device:
netbox_url: http://url.goes.here
netbox_token: 754544444444404509504959433333333
data:
name: "{{ item }}"
device_type: "{{ item }}"
device_role: "Core Switch"
site: Site
#serial_number: "number"
state: present
loop:
- "{{ dev_names }}"
- "{{ dev_types }} "
This works well only with one list, which means that if I comment out one of the two lists I get no errors.
The error I am getting when I run this task with both my lists is this:
File "/tmp/poioanni/ansible_netbox_device_payload_5W1o8U/ansible_netbox_device_payload.zip/ansible/module_utils/net_tools/netbox/netbox_utils.py", line 352, in normalize_data
AttributeError: 'list' object has no attribute 'lower'
failed: [localhost] (item=[u'nameisone', u'nameistwo']) => {
"ansible_loop_var": "item",
"changed": false,
"item": [
"nameisone",
"nameistwo"
],
Any ideas ?
If you are sure your lists are synchronized, you can use the zip filter, as demonstrated in the below test.yml MVCE playbook.
---
- name: Zip demo
hosts: localhost
gather_facts: false
vars:
"dev_names": [
"nameisone",
"nameistwo"
]
"dev_types": [
"type_one",
"type_two"
]
tasks:
- name: demonstrate how to use the zip filter with a loop
debug:
msg: "Element from first list: {{ item.0 }}. Element from second list: {{ item.1 }}"
loop: "{{ dev_names | zip(dev_types) | list }}"
Which gives:
$ ansible-playbook test.yml
PLAY [Zip demo] ************************************************************************************************************************************************************************************************************************
TASK [demonstrate how to use the zip filter with a loop] *******************************************************************************************************************************************************************************
ok: [localhost] => (item=['nameisone', 'type_one']) => {
"msg": "Element from first list: nameisone. Element from second list: type_one"
}
ok: [localhost] => (item=['nameistwo', 'type_two']) => {
"msg": "Element from first list: nameistwo. Element from second list: type_two"
}
PLAY RECAP *****************************************************************************************************************************************************************************************************************************
localhost : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
Related
below is output of the "RESULT":
stdout:
- |-
ospf T1 VRF vrf1
ospf T2 VRF vrf2
ospf T3 VRF vrf3
stdout_lines:
- ospf T1 VRF vrf1
- ospf T2 VRF vrf2
- ospf T3 VRF vrf3
I want output in list and in dictionary:
1st output will be list. list will have following:
- T1
- T2
- T3
2nd output will be list like below:
ospf_vrf:
- vrf: vrf1
process: T1
- vrf: vrf2
process: T2
- vrf: vrf3
process: T3
3rd output will be dictionary.
how to do that?
I would start by parsing the data from stdout via regex. Assuming the components in your output are always: ospf <process_value> VRF <vrf_falue>, the regex looks like this:
{{ stdout | regex_findall('^ospf (.+) VRF (.+)$', multiline=True) }}
Based on this, you can then put the values into different forms and store them in variables:
- set_fact:
ospf: "{{ extracted | map('first') }}"
ospf_vrf: "{{ extracted | map('zip', ['process', 'vrf']) |
map('map', 'reverse') | map('community.general.dict') }}"
ospf_dict: "{{ dict(extracted) }}"
vrf_dict: "{{ dict(extracted | map('reverse')) }}"
vars:
extracted: "{{ stdout | regex_findall('^ospf (.+) VRF (.+)$', multiline=True) }}"
extracted is a helper with the regex values and the base for all other values
ospf is a list of the process_values
In each case the first element of the regex is taken.
ospf_vrf is a list of dicts
the keys process and vrf are assigned to the regex values
by reverse the keys are brought to the front
by community.general.dict a dict is created from the key-value pairs
ospf_dict: a dict based on process
vrf_dict: a dict based on vrf
Note: Both ospf_dict and vrf_dict will lose values if there are duplicates for the keys.
The output of the 4 variables looks like this:
TASK [debug] ***************************
ok: [localhost] => {
"ospf": [
"T1",
"T2",
"T3"
]
}
TASK [debug] ***************************
ok: [localhost] => {
"ospf_vrf": [
{
"process": "T1",
"vrf": "vrf1"
},
{
"process": "T2",
"vrf": "vrf2"
},
{
"process": "T3",
"vrf": "vrf3"
}
]
}
TASK [debug] ***************************
ok: [localhost] => {
"ospf_dict": {
"T1": "vrf1",
"T2": "vrf2",
"T3": "vrf3"
}
}
TASK [debug] ***************************
ok: [localhost] => {
"vrf_dict": {
"vrf1": "T1",
"vrf2": "T2",
"vrf3": "T3"
}
}
I have created this sample playbook for your requirements. I am not clear on third expected output. please clarify on that
- hosts: localhost
gather_facts: no
vars:
stdout_lines:
- ospf T1 VRF vrf1
- ospf T2 VRF vrf2
- ospf T3 VRF vrf3
tasks:
- set_fact:
first_output: "{{ first_output | default([]) + [ (item | split())[1] ] }}"
with_items:
"{{ stdout_lines }}"
- debug:
var: first_output
- set_fact:
second_output: "{{ second_output | default([]) + [ { 'vrf': (item | split())[3] , 'process': (item | split())[1] } ] }}"
with_items:
"{{ stdout_lines }}"
- debug:
var: second_output
I'm using Ansible to build inventories dynamically according to several parameters.
I get raw info from our CMDB's API, then use set_fact to format a list of FQDNs.
Those FQDNs always follow the same formula, so it looks easy, but Ansible only seems to apply the templating to the first item.
The code:
- name: populate list of all hosts
set_fact:
all_hosts: '{{ all_hosts + [ "{{item.value.fields.friendlyname | lower}}" "." "{{item.value.fields.friendlyname[:3]|lower}}" ".sncoia.lan" ] }}'
loop: "{{ hostname.json.objects | dict2items }}"
when:
- item.value.fields.friendlyname | length == 12
- debug: var=all_hosts
The result (abridged for brevity):
TASK [debug] **********************************************************************************************************************************************************************************
ok: [localhost] => {
"all_hosts": [
"devlappaps03.dev.sncoia.lan",
"{{item.value.fields.friendlyname | lower}}.{{item.value.fields.friendlyname[:3]|lower}}.sncoia.lan",
"{{item.value.fields.friendlyname | lower}}.{{item.value.fields.friendlyname[:3]|lower}}.sncoia.lan",
Given the data for testing
hostname:
json:
objects:
foo:
fields:
friendlyname: devlappaps03
bar:
fields:
friendlyname: devlappaps02
baz:
fields:
friendlyname: devlappaps01
Properly concatenate the FQDN. For example
- set_fact:
all_hosts: "{{ all_hosts|d([]) + [_fqdn] }}"
loop: "{{ hostname.json.objects|dict2items }}"
when: item.value.fields.friendlyname|length == 12
vars:
_fqdn_list:
- "{{ item.value.fields.friendlyname|lower }}"
- "{{ item.value.fields.friendlyname[:3]|lower }}"
- "sncoia.lan"
_fqdn: "{{ _fqdn_list|join('.') }}"
gives
all_hosts:
- devlappaps03.dev.sncoia.lan
- devlappaps02.dev.sncoia.lan
- devlappaps01.dev.sncoia.lan
I've a dictionary that I'm gathering through an API that looks like below,
ok: [localhost] => {
"filtered_inventory": [
{
"Manufacturer": "",
"Name": "daughtercard 0/0/CPU0",
"Serial_number": "serial",
"device_id": 287,
"part_id": "ASR9001-LC"
},
{
"Manufacturer": "",
"Name": "module mau 0/0/2/3",
"Serial_number": "serial",
"device_id": 287,
"part_id": "SFP-10G-SR"
},
I'm also using the below API to get the below values but it's stored as a list
ok: [localhost] => {
"devices": [
{
"device_id": 287,
"hostname": "example.test.com"
},
{
"device_id": 350,
"hostname": "example.test.se"
},
What I want to achive is, if device_id is the same in both the list and the dict then append the hostname to the dictionary. I've solved it with the below code but as I'm working with a big dictionary it's highly inefficiant, running it with the below loop takes around 1 hour and eats a ton of memory.
- name: Merge hostname with inventory
#no_log: True
set_fact:
merged_list: "{{ merged_list|default([]) +
[{'hostname': item[0].hostname|upper,
'device_id': item[0].device_id,
'serial': item[1].Serial_number,
'Name': item[1].Name,
'Manufacturer': item[1].Manufacturer,
'part_id': item[1].part_id}] }}"
when: "item[0].device_id == item[1].device_id"
loop: "{{ query('nested', devices, filtered_inventory) }}"
I've so far been thinking that this probably could be solved by only looping through the dictionary and then match it to the list but haven't figured out how to do that, any ideas?
I'm thinking the below code could be a start but I've tried a few different approaches but failed to get it to work.
- name: Merge hostname with inventory
#no_log: True
set_fact:
merged_list: "{{ merged_list|default([]) + [item|combine
({'device_id': item.device_id,
'serial': item.Serial_number,
'Name': item.Name,
'Manufacturer': item.Manufacturer,
'part_id': item.part_id,
'hostname': '' })] }}"
loop: "{{ filtered_inventory }}"
when:
- "item.device_id is in devices|json_query(query)"
vars:
query: "'[*].device_id'"
Create a dictionary of the hostnames, e.g.
- set_fact:
devices_dict: "{{ devices|items2dict(key_name='device_id', value_name='hostname') }}"
gives
devices_dict:
287: example.test.com
350: example.test.se
Then, use this dictionary to combine items of a modified list, e.g.
- set_fact:
fi2: "{{ fi2|default([]) +
[item|combine({'hostname': devices_dict[item.device_id]})] }}"
loop: "{{ filtered_inventory }}"
gives
fi2:
- Manufacturer: ''
Name: daughtercard 0/0/CPU0
Serial_number: serial
device_id: 287
hostname: example.test.com
part_id: ASR9001-LC
- Manufacturer: ''
Name: module mau 0/0/2/3
Serial_number: serial
device_id: 287
hostname: example.test.com
part_id: SFP-10G-SR
Q: "It takes around 6 minutes to run ..."
A: This is too much. Try the filter below
shell> cat filter_plugins/combine_attr.py
def combine_attr(l, d, k, v):
x = []
for i in l:
i[k] = d[i[v]]
x.append(i)
return x
class FilterModule(object):
''' Ansible filter. Add an attribute to the dictionaries in the list.'''
def filters(self):
return {
'combine_attr': combine_attr,
}
For example
- set_fact:
fi2: "{{ filtered_inventory|
combine_attr(devices_dict, 'hostname', 'device_id') }}"
I am trying to build a list of switch serial numbers using Ansible. The serial number is retrieved using IOS Facts and is easy but I cannot get the list no to be incremental, it just overwrites the list with the latest serial.
Here's the partial code I have:
tasks:
- name: Get all facts from ios devices
register: all_facts
ios_facts:
gather_subset: hardware
- name: Create list Serials
set_fact:
IOSserials: "{{ IOSserials + [ all_facts.ansible_facts.ansible_net_serialnum ] }}"
- name: Display list
debug:
msg: "The list is: {{ IOSserials }}"
And here's the result I get:
TASK [Create list Serials] *****************************************************
ok: [lab3650s2] => {"ansible_facts": {"IOSserials": ["FDO201XXXXD"]}, "changed": false}
ok: [lab3650s1] => {"ansible_facts": {"IOSserials": ["FDO192XXXXV"]}, "changed": false}
ok: [lab4500s1] => {"ansible_facts": {"IOSserials": ["FOX141XXXXV"]}, "changed": false}
TASK [Display list] ************************************************************
ok: [lab4500s1] => {
"msg": "The list is: ['FOX141XXXXV']"
}
ok: [lab3650s2] => {
"msg": "The list is: ['FDO201XXXXD']"
}
ok: [dev-lab3650s1] => {
"msg": "The list is: ['FDO192XXXXV']"
I'm trying to get a result like:
['FOX141XXXXV','FDO201XXXXD','FDO192XXXXV']"
It's possible to map extract. For example
- debug:
msg: "{{ ansible_play_hosts_all|map('extract', hostvars, 'IOSserials')|list }}"
run_once: true
gives
"msg": [
"FOX141XXXXV",
"FDO201XXXXD",
"FDO192XXXXV"
]
I am trying to look up the un used disks using ansible. So far I am able to save the unused disk paths in a variable. But I am getting a lot of other useless information such as plus signs and brackets. I was wondering how can I filter the list down and get the strings that include /dev.
This is the code I have so far:
- set_fact:
list_of_disks: []
- name: getting list of disks
set_fact:
list_of_disks: "[{{list_of_disks}} + '/dev/{{item.key}}']"
when:
- not item.value.partitions
- not item.value.holders
- not item.value.links.ids
- item.key | search ("sd")
with_dict: "{{ ansible_devices }}"
- name: display unused disks
debug:
var: list_of_disks.split("'")
And this is the output I am getting
"list_of_disks.split(\"'\")": [
"[[[] + ",
"/dev/sde",
"] + ",
"/dev/sdc",
"]"
]
}
I would like the output to look like
"/dev/sde",
"/dev/sdc",
Note: the disk names would change from one server to another. So I don't want to look for sde and sdc only. It should be able to filer down all disks that come up in the list.
Well, might not be exactly the format you want, but:
- debug:
msg: "{{ item.device }}"
with_items: "{{ ansible_facts.mounts }}"
when: "'/dev/' in item.device"
Should do the trick.
On my local, I got:
TASK [debug] **********************************************************************************************************************************
ok: [localhost] => (item=None) => {
"msg": "/dev/mapper/fedora-root"
}
ok: [localhost] => (item=None) => {
"msg": "/dev/nvme0n1p1"
}
ok: [localhost] => (item=None) => {
"msg": "/dev/mapper/fedora-home"
}
If you need them all on one line, then create a variable and append to it:
- set_fact:
list_of_disks=""
- set_fact:
list_of_disks: "{{ list_of_disks | regex_replace('^, ','') }}, {{ item.device }}"
with_items: "{{ ansible_facts.mounts }}"
when: "'/dev/' in item.device"
- debug:
msg: "{{ list_of_disks }}"
For this, I got:
TASK [debug] **********************************************************************************************************************************
ok: [localhost] => {
"msg": "/dev/mapper/fedora-root, /dev/nvme0n1p1, /dev/mapper/fedora-home"
}