Skip to content

Instantly share code, notes, and snippets.

@victorock
Created June 15, 2018 10:14
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save victorock/955eff5c716023a86bd214bcf8a1a046 to your computer and use it in GitHub Desktop.
Save victorock/955eff5c716023a86bd214bcf8a1a046 to your computer and use it in GitHub Desktop.
Ansible's Inventory Plugin for network discovery using snmp
# Copyright (c) 2018 Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
DOCUMENTATION = '''
name: "snmp"
plugin_type: "inventory"
version_added: "2.7"
authors:
- "Victor da Costa <@victorock>"
short_description: "Uses snmpv2 query to find devices."
description:
- "Use sysDescr to find hosts and group them by platform."
- "Platforms: asa, ios, iosxr, nxos, dellos, junos, aruba and eos."
extends_documentation_fragment:
- constructed
- inventory_cache
requirements:
- pysnmp library
- netaddr library
options:
network:
description: "CIDRs to scan, separated by comma."
type: string
default: "192.168.0.0/24"
port:
description: "SNMP Port"
type: integer
default: 161
exclude:
description: "CIDRs to exclude from the scan, separated by comma."
type: string
default: None
community:
description: "The SNMP communities to query, separated by comma."
type: string
default: "public"
notes:
- "Convenient way to generate inventory from existing networks"
- "TODO: async, snmpv3, platforms++"
'''
EXAMPLES = '''
# snmp.config file in YAML format
plugin: snmp
community: mycommunity, public, idontknow
networks: 192.168.99.99/27, 192.168.100.0/23
exclude: 192.168.100.1/32, 192.168.100.2/32
port: 161
'''
import re
import time
from ansible import constants as C
from ansible.errors import AnsibleParserError, AnsibleError
from ansible.module_utils._text import to_native
from ansible.plugins.inventory import BaseInventoryPlugin, Constructable, Cacheable
try:
import netaddr as pynetaddr
except:
raise AnsibleError('snmp inventory plugin requires the netapp library. Try: pip install netaddr')
try:
import pysnmp.hlapi as pysnmpClient
except:
raise AnsibleError('snmp inventory plugin requires the pysnmp library. Try: pip install pysnmp')
class Factory(object):
InventoryModule = None
BatchBank = None
SysDescrParser = None
SnmpClient = None
NetAddr = None
# TODO: Async with control of batch Size
class BatchBank(object):
BALANCE = 0
def __init__(self, balance=128):
BALANCE = balance
@staticmethod
def balance():
Factory.InventoryModule.display.vvvv(
(
"BatchBank.balance: {balance}"
).format(
balance=BatchBank.BALANCE
)
)
return BatchBank.BALANCE
@staticmethod
def debit(amount=1):
BatchBank.BALANCE = BatchBank.BALANCE - amount
Factory.InventoryModule.display.vvvv(
(
"BatchBank.debit: {amount}"
).format(
amount=amount
)
)
return BatchBank.BALANCE
@staticmethod
def credit(amount=1):
BatchBank.BALANCE = BatchBank.BALANCE + amount
Factory.InventoryModule.display.vvvv(
(
"BatchBank.credit: {amount}"
).format(
amount=amount
)
)
return BatchBank.BALANCE
# TODO: Add additional platforms
class SysDescrParser(object):
@staticmethod
def platforms():
''' New devices/platform must be populated HERE
'''
return {
'asa': re.compile(r'^Cisco\sAdaptive\sSecurity\sAppliance'),
'ios': re.compile(r'^Cisco\sIOS\sSoftware'),
'iosxr': re.compile(r'^Cisco\sIOS\sXR\sSoftware'),
'nxos': re.compile(r'^Cisco\sNexus\sOperating\sSystem'),
'dellos': re.compile(r'^Dell\sApplication\sSoftware'),
'junos': re.compile(r'^JUNOS'),
'aruba': re.compile(r'^Aruba\sOperating\sSystem\sSoftware'),
'eos': re.compile(r'^Arista')
}
@staticmethod
def get_platform(text):
Factory.InventoryModule.display.vvvv(
(
"sysDescrParser.get_platform: {text}"
).format(
text=text
)
)
platforms = SysDescrParser.platforms()
for platform, rule in platforms.items():
if rule.match(text):
Factory.InventoryModule.display.vvvv(
(
"sysDescrParser.get_platform: "
"match {platform}"
).format(
platform=platform
)
)
return platform
platform = 'unknown'
Factory.InventoryModule.display.vvvv(
(
"SysDescrParser.get_platform: "
"match {platform}"
).format(
platform=platform
)
)
return platform
# TODO: snmpv3
class SnmpClient(object):
def __init__(
self, target='localhost', port=161,
community='public', mib='SNMPv2-MIB', oid='sysDescr'
):
self._target = target
self._port = port
self._community = community
self._mib = mib
self._oid = oid
self._snmpEngine = pysnmpClient.SnmpEngine()
def _objectIdentity(self):
return pysnmpClient.ObjectIdentity(self._mib, self._oid, 0)
def _communityData(self):
return pysnmpClient.CommunityData(self._community)
def _udpTransportTarget(self):
return pysnmpClient.UdpTransportTarget(
(self._target, self._port),
timeout=2.0,
retries=2
)
def _contextData(self):
return pysnmpClient.ContextData()
def _objectType(self):
return pysnmpClient.ObjectType(self._objectIdentity())
# TODO: Make it async
def run(self):
return next(
pysnmpClient.getCmd(
self._snmpEngine,
self._communityData(),
self._udpTransportTarget(),
self._contextData(),
self._objectType()
)
)
class InventoryModule(BaseInventoryPlugin, Constructable, Cacheable):
NAME = 'snmp'
def __init__(self):
super(InventoryModule, self).__init__()
def _discovery(self):
network_whitelist = self._options.get('network', '192.168.0.0/24')
network_whitelist = network_whitelist.strip().split(',')
ip_whitelist = Factory.NetAddr.IPSet(network_whitelist)
network_blacklist = self._options.get('exclude', '127.0.0.1')
network_blacklist = network_blacklist.strip().split(',')
ip_blacklist = Factory.NetAddr.IPSet(network_blacklist)
snmp_communities = self._options.get('community', 'public')
snmp_communities = snmp_communities.strip().split(',')
# Loop over ips in the network range
for ip in ip_whitelist:
Factory.SnmpClient._target = to_native(ip)
Factory.SnmpClient._port = self._options.get('port', 161)
Factory.SnmpClient._port = to_native(Factory.SnmpClient._port)
if Factory.SnmpClient._target in ip_blacklist:
Factory.InventoryModule.display.vvv(
(
"InventoryModule._discovery: "
"ignoring {host} in exclude list"
).format(
host=Factory.SnmpClient._target
)
)
continue
# Loop over communities
for snmp_community in snmp_communities:
snmp_community = snmp_community.strip()
Factory.SnmpClient._community = to_native(snmp_community)
try:
Factory.InventoryModule.display.vvvv(
(
"InventoryModule._discovery: "
"processing {host} ({community})"
).format(
host=Factory.SnmpClient._target,
community=Factory.SnmpClient._community
)
)
(
errorIndication,
errorStatus,
errorIndex,
varBinds
) = Factory.SnmpClient.run()
except Exception as e:
Factory.InventoryModule.display.vvvv(
(
"InventoryModule._discovery: "
"bypassing {host}"
).format(
host=Factory.SnmpClient._target
)
)
continue
if errorIndication:
errorIndication = to_native(errorIndication)
Factory.InventoryModule.display.vvvv(
(
"InventoryModule._discovery errorIndication: "
"{error}"
).format(
error=errorIndication
)
)
continue
elif errorStatus:
errorStatus = to_native(errorStatus)
Factory.InventoryModule.display.vvvv(
(
"InventoryModule._discovery errorStatus: "
"{error}"
).format(
error=errorStatus
)
)
continue
else:
for snmp_oid, snmp_text in varBinds:
snmp_text = to_native(snmp_text)
group_name = Factory.SysDescrParser.get_platform(snmp_text)
Factory.InventoryModule.inventory.add_group(group_name)
Factory.InventoryModule.display.vv(
(
"InventoryModule._discovery: "
"add_group({group})"
).format(
group=group_name
)
)
Factory.InventoryModule.inventory.set_variable(
group_name,
'ansible_network_os',
group_name
)
Factory.InventoryModule.display.vv(
(
"InventoryModule._discovery: "
"set_variable({group}, 'ansible_network_os', {group})"
).format(
group=group_name
)
)
Factory.InventoryModule.inventory.add_host(
Factory.SnmpClient._target,
group=group_name
)
Factory.InventoryModule.display.vv(
(
"InventoryModule._discovery: "
"add_host({host}, {group})"
).format(
host=Factory.SnmpClient._target,
group=group_name
)
)
# TODO: CACHE
def parse(self, inventory, loader, path, cache=False):
super(InventoryModule, self).parse(inventory, loader, path, cache)
self._options = self._read_config_data(path)
Factory.InventoryModule = self
Factory.SysDescrParser = SysDescrParser()
Factory.BatchBank = BatchBank()
Factory.SnmpClient = SnmpClient()
Factory.NetAddr = pynetaddr
self._discovery()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment