Skip to content

Commit b3f647b

Browse files
committed
Completely rewritten
1 parent 1d35de1 commit b3f647b

File tree

2 files changed

+123
-152
lines changed

2 files changed

+123
-152
lines changed

swinds.ini

+29-3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,30 @@
1+
; Connection details for Solarwinds server
12
[solarwinds]
2-
npm_server = 10.10.10.10
3-
npm_user = ansible
4-
npm_password = password
3+
npm_server = 172.16.224.11
4+
npm_user = netadmin
5+
npm_password = unisystems
6+
7+
; Define interesting host fields
8+
[hostfields]
9+
; Mandatory fields
10+
sysname = SysName
11+
ansible_host = IP_Address
12+
; Optional fields (more may be added)
13+
ios_image = IOSImage
14+
ios_version = IOSVersion
15+
16+
; Define interesting groups and list SWQL queries for them
17+
; Pay attention: SWQL aliases must match their respective group names (eg dpt, wan1, wan2 etc); case-sensitive
18+
[groupings]
19+
dpt = SELECT NCP.Department as dpt, N.SysName, N.IP_Address
20+
FROM Orion.Nodes as N
21+
JOIN Orion.NodesCustomProperties as NCP on N.NodeID = NCP.NodeID
22+
WHERE N.Vendor = 'Cisco' AND NCP.Department != ''
23+
wan1 = SELECT NCP.WAN_Primary as wan1, N.SysName, N.IP_Address
24+
FROM Orion.Nodes as N
25+
JOIN Orion.NodesCustomProperties as NCP on N.NodeID = NCP.NodeID
26+
WHERE N.Vendor = 'Cisco' AND NCP.WAN_Primary != ''
27+
wan2 = SELECT NCP.WAN_Secondary as wan2, N.SysName, N.IP_Address
28+
FROM Orion.Nodes as N
29+
JOIN Orion.NodesCustomProperties as NCP on N.NodeID = NCP.NodeID
30+
WHERE N.Vendor = 'Cisco' AND NCP.WAN_Secondary != ''

swinds.py

+94-149
Original file line numberDiff line numberDiff line change
@@ -1,159 +1,104 @@
11
#!/usr/bin/env python
22

3-
4-
'''
5-
Custom dynamic inventory script for Ansible and Solar Winds, in Python.
6-
This was tested on Python 2.7.6, Orion 2016.2.100, and Ansible 2.3.0.0.
7-
8-
(c) 2017, Chris Babcock ([email protected])
9-
10-
https://github.com/cbabs/solarwinds-ansible-inv
11-
12-
This program is free software: you can redistribute it and/or modify
13-
it under the terms of the GNU General Public License as published by
14-
the Free Software Foundation, either version 3 of the License, or
15-
(at your option) any later version.
16-
17-
This program is distributed in the hope that it will be useful,
18-
but WITHOUT ANY WARRANTY; without even the implied warranty of
19-
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20-
GNU General Public License for more details.
21-
22-
You should have received a copy of the GNU General Public License
23-
along with this program. If not, see <http://www.gnu.org/licenses/>.
24-
25-
NOTE: This software is free to use for any reason or purpose. That said, the
26-
author request that improvements be submitted back to the repo or forked
27-
to something public.
28-
29-
'''
30-
import argparse
31-
import ConfigParser
3+
# ======================================================================================================================
4+
# IMPORTS
5+
# ======================================================================================================================
6+
import configparser
327
import requests
338
import re
34-
35-
try:
36-
import json
37-
except ImportError:
38-
import simplejson as json
39-
40-
41-
config_file = 'swinds.ini'
42-
43-
# Get configuration variables
44-
config = ConfigParser.ConfigParser()
9+
import json
10+
import os
11+
12+
13+
# ======================================================================================================================
14+
# FUNCTION DEFINITIONS
15+
# ======================================================================================================================
16+
# Creates an empty inventory
17+
def empty_inventory():
18+
return {'_meta': {'hostvars': {}}}
19+
20+
# Modify group name to comply with Ansible's limitations (no special characters; underscores only)
21+
def clean_group_name(group):
22+
group = re.sub('[^A-Za-z0-9]+', '_', group)
23+
return group
24+
25+
# Remove domain ('efka.gr') from hostname
26+
def clean_host_name(host):
27+
host = host.split('.')[0]
28+
return host
29+
30+
# Perform SWQL query over REST API using the given payload string
31+
def swql_query(payload):
32+
# Each SELECT statement must be prepended with 'query= '
33+
payload = "query= " + payload
34+
35+
# Perform SWQL query for groups and validate results
36+
req = requests.get(url, params=payload, verify=False, auth=(user, password))
37+
jsonget = req.json()
38+
jsonget = eval(json.dumps(jsonget))
39+
40+
return jsonget['results']
41+
42+
# Populate the hosts part of the inventory
43+
def get_hosts(hostfields, inventory):
44+
# Construct 'query' including all the hostfields defined in the config_file
45+
query = "SELECT " + ", ".join(list(hostfields.values())) + " FROM Orion.Nodes WHERE Vendor = 'Cisco'"
46+
47+
# Retrieve complete list of hosts
48+
hosts = swql_query(query)
49+
50+
# Populate inventory with hosts containing values
51+
for host in hosts:
52+
host[sysname] = clean_host_name(host[sysname]) # Remove domain suffix from hostname
53+
hDict = {k:host[v] for (k,v) in hostfields.items() if k!='sysname'} # Use comprehension to construct values dict
54+
inventory['_meta']['hostvars'][host[sysname]] = hDict # Append consctucted values dict under host
55+
return inventory
56+
57+
# Populate the groups part of the inventory
58+
def get_groups(groupings, inventory):
59+
# Perform the following actions for each grouping defined in the config_items
60+
for grouping, query in groupings.items():
61+
# Retrieve complete list of hosts
62+
hosts = swql_query(query)
63+
64+
# Populate inventory with groups containing member hosts
65+
for host in hosts:
66+
host[sysname] = clean_host_name(host[sysname]) # Remove domain suffix from hostname
67+
host[grouping] = grouping + '_' + clean_group_name(host[grouping]) # Replace special chars with underscores
68+
if host[grouping] in inventory: # If group already exists in inventory
69+
inventory[host[grouping]]['hosts'].append(host[sysname]) # ...append host to it
70+
else: # ...or else
71+
inventory[host[grouping]] = {'hosts': [host[sysname]]} # ...create group and add host to it
72+
return inventory
73+
74+
75+
# ======================================================================================================================
76+
# MAIN PROGRAM
77+
# ======================================================================================================================
78+
# Read config file
79+
pwd = os.path.dirname(__file__) # Assuming that 'swinds.ini' exists
80+
config_file = pwd + '/swinds.ini' # in the same folder as 'swinds.py'
81+
config = configparser.ConfigParser()
4582
config.readfp(open(config_file))
4683

84+
# Get all configuration variables from config file using dict comprehension
85+
config_items = {section:dict(config.items(section)) for section in config.sections()}
4786

87+
# Define first-level variables parsing config_items directly
88+
server = config_items['solarwinds']['npm_server']
89+
user = config_items['solarwinds']['npm_user']
90+
password = config_items['solarwinds']['npm_password']
91+
hostfields = config_items['hostfields']
92+
groupings = config_items['groupings']
4893

49-
# Orion Server IP or DNS/hostname
50-
server = config.get('solarwinds', 'npm_server')
51-
# Orion Username
52-
user = config.get('solarwinds', 'npm_user')
53-
# Orion Password
54-
password = config.get('solarwinds', 'npm_password')
55-
# Field for groups
56-
groupField = 'GroupName'
57-
# Field for host
58-
hostField = 'SysName'
59-
60-
payload = "query=SELECT C.Name as GroupName, N.SysName FROM Orion.Nodes as N JOIN Orion.ContainerMemberSnapshots as CM on N.NodeID = CM.EntityID JOIN Orion.Container as C on CM.ContainerID=C.ContainerID WHERE CM.EntityDisplayName = 'Node' AND N.Vendor = 'Cisco'"
61-
62-
use_groups = True
63-
parentField = 'ParentGroupName'
64-
childField = 'ChildGroupName'
94+
# Define second-level variables that are products of the first-level ones
95+
url = "https://"+server+":17778/SolarWinds/InformationService/v3/Json/Query"
96+
sysname = hostfields['sysname']
6597

66-
group_payload = "query=SELECT C.Name as ParentGroupName, CM.Name as ChildGroupName FROM Orion.ContainerMemberSnapshots as CM JOIN Orion.Container as C on CM.ContainerID=C.ContainerID WHERE CM.EntityDisplayName = 'Group'"
98+
# Create the inventory for Ansible
99+
inventory = {'_meta': {'hostvars': {}}} # Initialize inventory
100+
inventory = get_hosts(hostfields, inventory) # Add hosts with vars
101+
inventory = get_groups(groupings, inventory) # Add groups with members
67102

68-
#payload = "query=SELECT+" + hostField + "+," + groupField + "+FROM+Orion.Nodes"
69-
url = "https://"+server+":17778/SolarWinds/InformationService/v3/Json/Query"
70-
req = requests.get(url, params=payload, verify=False, auth=(user, password))
71-
72-
jsonget = req.json()
73-
74-
75-
class SwInventory(object):
76-
77-
# CLI arguments
78-
def read_cli(self):
79-
parser = argparse.ArgumentParser()
80-
parser.add_argument('--host')
81-
parser.add_argument('--list', action='store_true')
82-
self.options = parser.parse_args()
83-
84-
def __init__(self):
85-
self.inventory = {}
86-
self.read_cli_args()
87-
88-
# Called with `--list`.
89-
if self.args.list:
90-
self.inventory = self.get_list()
91-
if use_groups:
92-
self.groups = self.get_groups()
93-
self.add_groups_to_hosts(self.groups)
94-
# Called with `--host [hostname]`.
95-
elif self.args.host:
96-
# Not implemented, since we return _meta info `--list`.
97-
self.inventory = self.empty_inventory()
98-
# If no groups or vars are present, return empty inventory.
99-
else:
100-
self.inventory = self.empty_inventory()
101-
102-
print(json.dumps(self.inventory, indent=2))
103-
def get_list(self):
104-
hostsData = jsonget
105-
dumped = eval(json.dumps(jsonget))
106-
107-
# Inject data below to speed up script
108-
final_dict = {'_meta': {'hostvars': {}}}
109-
110-
# Loop hosts in groups and remove special chars from group names
111-
for m in dumped['results']:
112-
# Allow Upper/lower letters and numbers. Replace everything else with underscore
113-
m[groupField] = self.clean_inventory_item(m[groupField])
114-
if m[groupField] in final_dict:
115-
final_dict[m[groupField]]['hosts'].append(m[hostField])
116-
else:
117-
final_dict[m[groupField]] = {'hosts': [m[hostField]]}
118-
return final_dict
119-
120-
#if self.args.groups:
121-
def get_groups(self):
122-
req = requests.get(url, params=group_payload, verify=False, auth=(user, password))
123-
hostsData = req.json()
124-
dumped = eval(json.dumps(hostsData))
125-
126-
parentField = 'ParentGroupName'
127-
childField = 'ChildGroupName'
128-
final_dict = {}
129-
for m in dumped['results']:
130-
# Allow Upper/lower letters and numbers. Replace everything else with underscore
131-
m[parentField] = self.clean_inventory_item(m[parentField])
132-
m[childField] = self.clean_inventory_item(m[childField])
133-
if m[parentField] in final_dict:
134-
final_dict[m[parentField]]['children'].append(m[childField])
135-
else:
136-
final_dict[m[parentField]] = {'children': [m[childField]]}
137-
return final_dict
138-
139-
def add_groups_to_hosts (self, groups):
140-
self.inventory.update(groups)
141-
142-
@staticmethod
143-
def clean_inventory_item(item):
144-
item = re.sub('[^A-Za-z0-9]+', '_', item)
145-
return item
146-
147-
# Empty inventory for testing.
148-
def empty_inventory(self):
149-
return {'_meta': {'hostvars': {}}}
150-
151-
# Read the command line args passed to the script.
152-
def read_cli_args(self):
153-
parser = argparse.ArgumentParser()
154-
parser.add_argument('--list', action='store_true')
155-
parser.add_argument('--host', action='store')
156-
self.args = parser.parse_args()
157-
158-
# Get the inventory.
159-
SwInventory()
103+
# Print the inventory in JSON format
104+
print(json.dumps(inventory, indent=2))

0 commit comments

Comments
 (0)