├── swinds.ini ├── README.md └── swinds.py /swinds.ini: -------------------------------------------------------------------------------- 1 | [solarwinds] 2 | npm_server = 10.10.10.10 3 | npm_user = ansible 4 | npm_password = password 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # swinds-ansible-inv 2 | Dynamic Inventory for Solar Winds hosts in Ansible 3 | 4 | This python script connects to Solar Winds via rest API to create a dynamic inventory in Ansible. JSON data is pulled into the python script via rest https API call to Orion. Groups are based on 'Vendor'. It pulls the 'IPAddress' for hosts field. The impetus for this script was for network devices. As such, the 'IPAddress' was chosen over say hostname. If one wanted to use anisble for servers with DNS using hostnames, replacing the 'IPAdrress' string to 'SysName' should produce that result. One could also change the 'Vendor' to somethign else like the OS version. I would recommend downloading the SWQL Studio and look under Orion.Nodes table for more options. 5 | 6 | Any suggestions/code to improve would be awesome. 7 | -------------------------------------------------------------------------------- /swinds.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 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 (chris@bluegreenit.com) 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 . 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 32 | import requests 33 | 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() 45 | config.readfp(open(config_file)) 46 | 47 | 48 | 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' 65 | 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'" 67 | 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() 160 | --------------------------------------------------------------------------------