From 19c47e007cc0b8af7d8a0d4f2ef839f9caa000e1 Mon Sep 17 00:00:00 2001
From: ogenstad <patrick@ogenstad.com>
Date: Mon, 3 Nov 2014 21:44:45 +0100
Subject: [PATCH 1/3] Snmp module for Ansible

---
 net_infrastructure/snmp_facts.py | 365 +++++++++++++++++++++++++++++++
 1 file changed, 365 insertions(+)
 create mode 100755 net_infrastructure/snmp_facts.py

diff --git a/net_infrastructure/snmp_facts.py b/net_infrastructure/snmp_facts.py
new file mode 100755
index 00000000000..3f54569d018
--- /dev/null
+++ b/net_infrastructure/snmp_facts.py
@@ -0,0 +1,365 @@
+#!/usr/bin/python
+
+# This file is part of Networklore's snmp library for Ansible
+#
+# The module is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# The module is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible.  If not, see <http://www.gnu.org/licenses/>.
+
+
+DOCUMENTATION = '''
+---
+module: snmp_facts
+author: Patrick Ogenstad (@networklore)
+notes:
+    - Version 0.7
+short_description: Retrive facts for a device using SNMP.
+description:
+    - Retrieve facts for a device using SNMP, the facts will be
+      inserted to the ansible_facts key.
+requirements:
+    - pysnmp
+options:
+    host:
+        description:
+            - Set to {{ inventory_hostname }}}
+        required: true
+    version:
+        description:
+            - SNMP Version to use, v2/v2c or v3
+        choices: [ 'v2', 'v2c', 'v3' ]
+        required: true
+    community:
+        description:
+            - The SNMP community string, required if version is v2/v2c
+        required: false
+    level:
+        description:
+            - Authentication level, required if version is v3
+        choices: [ 'authPriv', 'authNoPriv' ]
+        required: false
+    username:
+        description:
+            - Username for SNMPv3, required if version is v3
+        required: false
+    integrity:
+        description:
+            - Hashing algoritm, required if version is v3
+        choices: [ 'md5', 'sha' ]
+        required: false
+    authkey:
+        description:
+            - Authentication key, required if version is v3
+        required: false
+    privacy:
+        description:
+            - Encryption algoritm, required if level is authPriv
+        choices: [ 'des', 'aes' ]
+        required: false
+    privkey:
+        description:
+            - Encryption key, required if version is authPriv
+        required: false
+'''
+
+EXAMPLES = '''
+# Gather facts with SNMP version 2
+- snmp_facts: host={{ inventory_hostname }} version=2c community=public
+
+# Gather facts using SNMP version 3
+- snmp_facts:
+    host={{ inventory_hostname }}
+    version=v3
+    level=authPriv
+    integrity=sha
+    privacy=aes
+    username=snmp-user
+    authkey=abc12345
+    privkey=def6789
+'''
+
+from ansible.module_utils.basic import *
+from collections import defaultdict
+
+try:
+    from pysnmp.entity.rfc3413.oneliner import cmdgen
+    has_pysnmp = True
+except:
+    has_pysnmp = False
+
+class DefineOid(object):
+
+    def __init__(self,dotprefix=False):
+        if dotprefix:
+            dp = "."
+        else:
+            dp = ""
+
+        # From SNMPv2-MIB
+        self.sysDescr    = dp + "1.3.6.1.2.1.1.1.0"
+        self.sysObjectId = dp + "1.3.6.1.2.1.1.2.0"
+        self.sysUpTime   = dp + "1.3.6.1.2.1.1.3.0"
+        self.sysContact  = dp + "1.3.6.1.2.1.1.4.0"
+        self.sysName     = dp + "1.3.6.1.2.1.1.5.0"
+        self.sysLocation = dp + "1.3.6.1.2.1.1.6.0"
+        
+        # From IF-MIB
+        self.ifIndex       = dp + "1.3.6.1.2.1.2.2.1.1"
+        self.ifDescr       = dp + "1.3.6.1.2.1.2.2.1.2"
+        self.ifMtu         = dp + "1.3.6.1.2.1.2.2.1.4"
+        self.ifSpeed       = dp + "1.3.6.1.2.1.2.2.1.5"
+        self.ifPhysAddress = dp + "1.3.6.1.2.1.2.2.1.6"
+        self.ifAdminStatus = dp + "1.3.6.1.2.1.2.2.1.7"
+        self.ifOperStatus  = dp + "1.3.6.1.2.1.2.2.1.8"
+        self.ifAlias       = dp + "1.3.6.1.2.1.31.1.1.1.18"
+
+        # From IP-MIB
+        self.ipAdEntAddr    = dp + "1.3.6.1.2.1.4.20.1.1"
+        self.ipAdEntIfIndex = dp + "1.3.6.1.2.1.4.20.1.2"
+        self.ipAdEntNetMask = dp + "1.3.6.1.2.1.4.20.1.3"
+        
+
+def decode_hex(hexstring):
+ 
+    if len(hexstring) < 3:
+        return hexstring
+    if hexstring[:2] == "0x":
+        return hexstring[2:].decode("hex")
+    else:
+        return hexstring
+
+def decode_mac(hexstring):
+
+    if len(hexstring) != 14:
+        return hexstring
+    if hexstring[:2] == "0x":
+        return hexstring[2:]
+    else:
+        return hexstring
+
+def lookup_adminstatus(int_adminstatus):
+    adminstatus_options = {
+                            1: 'up',
+                            2: 'down',
+                            3: 'testing'
+                          }
+    if int_adminstatus in adminstatus_options.keys():
+        return adminstatus_options[int_adminstatus]
+    else:
+        return ""
+
+def lookup_operstatus(int_operstatus):
+    operstatus_options = {
+                           1: 'up',
+                           2: 'down',
+                           3: 'testing',
+                           4: 'unknown',
+                           5: 'dormant',
+                           6: 'notPresent',
+                           7: 'lowerLayerDown'
+                         }
+    if int_operstatus in operstatus_options.keys():
+        return operstatus_options[int_operstatus]
+    else:
+        return ""
+
+def main():
+    module = AnsibleModule(
+        argument_spec=dict(
+            host=dict(required=True),
+            version=dict(required=True, choices=['v2', 'v2c', 'v3']),
+            community=dict(required=False, default=False),
+            username=dict(required=False),
+            level=dict(required=False, choices=['authNoPriv', 'authPriv']),
+            integrity=dict(required=False, choices=['md5', 'sha']),
+            privacy=dict(required=False, choices=['des', 'aes']),
+            authkey=dict(required=False),
+            privkey=dict(required=False),
+            removeplaceholder=dict(required=False)),
+            required_together = ( ['username','level','integrity','authkey'],['privacy','privkey'],),
+        supports_check_mode=False)
+
+    m_args = module.params
+
+    if not has_pysnmp:
+        module.fail_json(msg='Missing required pysnmp module (check docs)')
+
+    cmdGen = cmdgen.CommandGenerator()
+
+    # Verify that we receive a community when using snmp v2
+    if m_args['version'] == "v2" or m_args['version'] == "v2c":
+        if m_args['community'] == False:
+            module.fail_json(msg='Community not set when using snmp version 2')
+            
+    if m_args['version'] == "v3":
+        if m_args['username'] == None:
+            module.fail_json(msg='Username not set when using snmp version 3')
+
+        if m_args['level'] == "authPriv" and m_args['privacy'] == None:
+            module.fail_json(msg='Privacy algorithm not set when using authPriv')
+
+            
+        if m_args['integrity'] == "sha":
+            integrity_proto = cmdgen.usmHMACSHAAuthProtocol
+        elif m_args['integrity'] == "md5":
+            integrity_proto = cmdgen.usmHMACMD5AuthProtocol
+
+        if m_args['privacy'] == "aes":
+            privacy_proto = cmdgen.usmAesCfb128Protocol
+        elif m_args['privacy'] == "des":
+            privacy_proto = cmdgen.usmDESPrivProtocol
+    
+    # Use SNMP Version 2
+    if m_args['version'] == "v2" or m_args['version'] == "v2c":
+        snmp_auth = cmdgen.CommunityData(m_args['community'])
+
+    # Use SNMP Version 3 with authNoPriv
+    elif m_args['level'] == "authNoPriv":
+        snmp_auth = cmdgen.UsmUserData(m_args['username'], authKey=m_args['authkey'], authProtocol=integrity_proto)
+
+    # Use SNMP Version 3 with authPriv
+    else:
+        snmp_auth = cmdgen.UsmUserData(m_args['username'], authKey=m_args['authkey'], privKey=m_args['privkey'], authProtocol=integrity_proto, privProtocol=privacy_proto)
+
+    # Use p to prefix OIDs with a dot for polling
+    p = DefineOid(dotprefix=True)
+    # Use v without a prefix to use with return values
+    v = DefineOid(dotprefix=False)
+
+    Tree = lambda: defaultdict(Tree)
+                               
+    results = Tree()
+            
+    errorIndication, errorStatus, errorIndex, varBinds = cmdGen.getCmd(
+        snmp_auth,
+        cmdgen.UdpTransportTarget((m_args['host'], 161)),
+        cmdgen.MibVariable(p.sysDescr,),
+        cmdgen.MibVariable(p.sysObjectId,), 
+        cmdgen.MibVariable(p.sysUpTime,),
+        cmdgen.MibVariable(p.sysContact,), 
+        cmdgen.MibVariable(p.sysName,),
+        cmdgen.MibVariable(p.sysLocation,),
+    )
+
+
+    if errorIndication:
+        module.fail_json(msg=str(errorIndication))
+
+    for oid, val in varBinds:
+        current_oid = oid.prettyPrint()
+        current_val = val.prettyPrint()
+        if current_oid == v.sysDescr:
+            results['ansible_sysdescr'] = decode_hex(current_val)
+        elif current_oid == v.sysObjectId:
+            results['ansible_sysobjectid'] = current_val
+        elif current_oid == v.sysUpTime:
+            results['ansible_sysuptime'] = current_val
+        elif current_oid == v.sysContact:
+            results['ansible_syscontact'] = current_val
+        elif current_oid == v.sysName:
+            results['ansible_sysname'] = current_val
+        elif current_oid == v.sysLocation:
+            results['ansible_syslocation'] = current_val
+
+    errorIndication, errorStatus, errorIndex, varTable = cmdGen.nextCmd(
+        snmp_auth,
+        cmdgen.UdpTransportTarget((m_args['host'], 161)), 
+        cmdgen.MibVariable(p.ifIndex,),
+        cmdgen.MibVariable(p.ifDescr,),
+        cmdgen.MibVariable(p.ifMtu,),
+        cmdgen.MibVariable(p.ifSpeed,),
+        cmdgen.MibVariable(p.ifPhysAddress,),
+        cmdgen.MibVariable(p.ifAdminStatus,),
+        cmdgen.MibVariable(p.ifOperStatus,),
+        cmdgen.MibVariable(p.ipAdEntAddr,), 
+        cmdgen.MibVariable(p.ipAdEntIfIndex,), 
+        cmdgen.MibVariable(p.ipAdEntNetMask,), 
+
+        cmdgen.MibVariable(p.ifAlias,),
+    )
+ 
+
+    if errorIndication:
+        module.fail_json(msg=str(errorIndication))
+
+    interface_indexes = []
+    
+    all_ipv4_addresses = []     
+    ipv4_networks = Tree()
+
+    for varBinds in varTable:
+        for oid, val in varBinds:
+            current_oid = oid.prettyPrint()
+            current_val = val.prettyPrint()
+            if v.ifIndex in current_oid:
+                ifIndex = int(current_oid.rsplit('.', 1)[-1])
+                results['ansible_interfaces'][ifIndex]['ifindex'] = current_val
+                interface_indexes.append(ifIndex)
+            if v.ifDescr in current_oid:
+                ifIndex = int(current_oid.rsplit('.', 1)[-1])
+                results['ansible_interfaces'][ifIndex]['name'] = current_val
+            if v.ifMtu in current_oid:
+                ifIndex = int(current_oid.rsplit('.', 1)[-1])
+                results['ansible_interfaces'][ifIndex]['mtu'] = current_val
+            if v.ifMtu in current_oid:
+                ifIndex = int(current_oid.rsplit('.', 1)[-1])
+                results['ansible_interfaces'][ifIndex]['speed'] = current_val
+            if v.ifPhysAddress in current_oid:
+                ifIndex = int(current_oid.rsplit('.', 1)[-1])
+                results['ansible_interfaces'][ifIndex]['mac'] = decode_mac(current_val)
+            if v.ifAdminStatus in current_oid:
+                ifIndex = int(current_oid.rsplit('.', 1)[-1])
+                results['ansible_interfaces'][ifIndex]['adminstatus'] = lookup_adminstatus(int(current_val))
+            if v.ifOperStatus in current_oid:
+                ifIndex = int(current_oid.rsplit('.', 1)[-1])
+                results['ansible_interfaces'][ifIndex]['operstatus'] = lookup_operstatus(int(current_val))
+            if v.ipAdEntAddr in current_oid:
+                curIPList = current_oid.rsplit('.', 4)[-4:]
+                curIP = ".".join(curIPList)
+                ipv4_networks[curIP]['address'] = current_val
+                all_ipv4_addresses.append(current_val)
+            if v.ipAdEntIfIndex in current_oid:
+                curIPList = current_oid.rsplit('.', 4)[-4:]
+                curIP = ".".join(curIPList)
+                ipv4_networks[curIP]['interface'] = current_val
+            if v.ipAdEntNetMask in current_oid:
+                curIPList = current_oid.rsplit('.', 4)[-4:]
+                curIP = ".".join(curIPList)
+                ipv4_networks[curIP]['netmask'] = current_val
+
+            if v.ifAlias in current_oid:
+                ifIndex = int(current_oid.rsplit('.', 1)[-1])
+                results['ansible_interfaces'][ifIndex]['description'] = current_val
+
+    interface_to_ipv4 = {}
+    for ipv4_network in ipv4_networks:
+        current_interface = ipv4_networks[ipv4_network]['interface']
+        current_network = {
+                            'address':  ipv4_networks[ipv4_network]['address'],
+                            'netmask':  ipv4_networks[ipv4_network]['netmask']
+                          }
+        if not current_interface in interface_to_ipv4:
+            interface_to_ipv4[current_interface] = []
+            interface_to_ipv4[current_interface].append(current_network)
+        else:
+            interface_to_ipv4[current_interface].append(current_network)
+
+    for interface in interface_to_ipv4:
+        results['ansible_interfaces'][int(interface)]['ipv4'] = interface_to_ipv4[interface]
+
+    results['ansible_all_ipv4_addresses'] = all_ipv4_addresses
+ 
+    module.exit_json(ansible_facts=results)
+    
+
+main()
+

From d4069bbc71bbd85f99b1e3c23d6d6bd09da3b82c Mon Sep 17 00:00:00 2001
From: ogenstad <patrick@ogenstad.com>
Date: Mon, 1 Dec 2014 19:34:39 +0100
Subject: [PATCH 2/3] added_version -> 1.9 and file move

---
 {net_infrastructure => network}/snmp_facts.py | 0
 1 file changed, 0 insertions(+), 0 deletions(-)
 rename {net_infrastructure => network}/snmp_facts.py (100%)

diff --git a/net_infrastructure/snmp_facts.py b/network/snmp_facts.py
similarity index 100%
rename from net_infrastructure/snmp_facts.py
rename to network/snmp_facts.py

From 24822322d4d3f9a8930e81bb93c18c85c398e422 Mon Sep 17 00:00:00 2001
From: ogenstad <patrick@ogenstad.com>
Date: Mon, 1 Dec 2014 19:39:49 +0100
Subject: [PATCH 3/3] 1.9

---
 network/snmp_facts.py | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/network/snmp_facts.py b/network/snmp_facts.py
index 3f54569d018..4d2d240ff28 100755
--- a/network/snmp_facts.py
+++ b/network/snmp_facts.py
@@ -19,9 +19,8 @@
 DOCUMENTATION = '''
 ---
 module: snmp_facts
+version_added: "1.9"
 author: Patrick Ogenstad (@networklore)
-notes:
-    - Version 0.7
 short_description: Retrive facts for a device using SNMP.
 description:
     - Retrieve facts for a device using SNMP, the facts will be