├── .gitignore ├── .travis.yml ├── HISTORY.rst ├── MANIFEST.in ├── README.md ├── README.rst ├── TODO.md ├── changelog.txt ├── compare-bash ├── nk-compare-configs.sh └── settings ├── lib └── nelkit │ ├── __init__.py │ ├── args │ ├── __init__.py │ ├── base.py │ └── snmp.py │ ├── cli │ ├── __init__.py │ ├── compare_configs.py │ └── snmp_deviceinfo.py │ ├── exceptions.py │ ├── globals.py │ ├── modules │ ├── __init__.py │ └── compare_configs │ │ ├── __init__.py │ │ └── settings.py │ ├── parsing │ ├── __init__.py │ └── yaml │ │ ├── __init__.py │ │ └── loader.py │ └── snmp │ ├── __init__.py │ └── handler.py ├── pylama.ini ├── setup.py ├── subscribe.html ├── templates └── network-device-template.numbers │ ├── Index.zip │ ├── Metadata │ ├── BuildVersionHistory.plist │ ├── DocumentIdentifier │ └── Properties.plist │ ├── preview-micro.jpg │ ├── preview-web.jpg │ └── preview.jpg ├── tests ├── modules │ └── compare_configs │ │ ├── data │ │ ├── base_rules.yml │ │ ├── configs │ │ │ └── basic │ │ │ │ ├── file_a.ios │ │ │ │ ├── file_b.ios │ │ │ │ └── file_c.ios │ │ └── invalid_rule_between_missing_start.yml │ │ └── test_compare_configs.py └── parsing │ └── yaml │ ├── data │ ├── base.yml │ └── invalid_yaml.yml │ └── test_parsing_yaml.py └── tox.ini /.gitignore: -------------------------------------------------------------------------------- 1 | .cache 2 | .coverage 3 | *.pyc 4 | dist/ 5 | *egg-info/ 6 | .tox 7 | dev/ 8 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | --- 2 | language: python 3 | python: 4 | - 2.7 5 | - 3.5 6 | - 3.6 7 | 8 | install: 9 | - pip install tox-travis 10 | - pip install coveralls 11 | script: 12 | - tox 13 | after_success: 14 | - coveralls 15 | -------------------------------------------------------------------------------- /HISTORY.rst: -------------------------------------------------------------------------------- 1 | .. :changelog: 2 | 3 | Release History 4 | --------------- 5 | 6 | 0.0.5 7 | +++++ 8 | 9 | * Allow configuration of baseline file for nk-compare-configs 10 | 11 | 0.0.4 12 | +++++ 13 | 14 | * Bugfixes for nk-snmp-deviceinfo 15 | 16 | 0.0.3 17 | +++++ 18 | 19 | * Speed improvements for nk-compare-configs 20 | 21 | 0.0.2 22 | +++++ 23 | 24 | * Added description for the tests used by nk-compare-configs 25 | 26 | 27 | 0.0.1 28 | +++++ 29 | 30 | First initial release 31 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include HISTORY.rst README.rst 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [](https://travis-ci.org/networklore/nelkit) 2 | [](https://coveralls.io/github/networklore/nelkit?branch=master) 3 | [](https://saythanks.io/to/ogenstad) 4 | 5 | NELKIT: A toolkit for network engineers 6 | ======================================= 7 | 8 | [Nelkit](https://networklore.com/nelkit/) is a collection of tools aimed to help network engineers. 9 | 10 | ### Installation 11 | 12 | Install using pip: 13 | 14 | ```bash 15 | 16 | $ pip install nelkit 17 | ``` 18 | 19 | ### Tools 20 | 21 | These tools are currently included: 22 | 23 | * [nk-compare-configs](https://networklore.com/nk-compare-configs/) - Preforms audits of configuration files for network devices 24 | * nk-snmp-deviceinfo - Polls a network device using SNMP to show vendor and version information 25 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | NELKIT: A toolkit for network engineers 2 | ======================================= 3 | 4 | Nelkit is a collection of tools aimed to help network engineers. 5 | 6 | Installation 7 | ------------ 8 | 9 | Install using pip: 10 | 11 | .. code-block:: bash 12 | 13 | $ pip install nelkit 14 | 15 | Tools 16 | ----- 17 | 18 | These tools are currently included: 19 | 20 | * nk-compare-configs - Preforms audits of configuration files for network devices 21 | * nk-snmp-deviceinfo - Polls a network device using SNMP to show vendor and version information 22 | -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | directory to configs 2 | 3 | rules file 4 | 5 | configs: 6 | - '/path/to/configs1' 7 | - '/path/to/configs2' 8 | 9 | 10 | functions 11 | - match 12 | - match_exclude 13 | - match_sort 14 | - match_exclude_sort 15 | - start_end 16 | - start_end_exclude 17 | -------------------------------------------------------------------------------- /changelog.txt: -------------------------------------------------------------------------------- 1 | - 2014-02-18 ------- 2 | 3 | Added the compare-bash directory 4 | nk-compare-configs.sh version 1.0 5 | 6 | - 2014-01-07 ------- 7 | 8 | First initial release 9 | network-template-device.numbers version 1.0 -------------------------------------------------------------------------------- /compare-bash/nk-compare-configs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | #========================================================== 3 | # LANG : Bash 4 | # NAME : nk-compare-configs.sh 5 | # AUTHOR : Patrick Ogenstad 6 | # VERSION : 1.0 7 | # DATE : 2014-02-18 8 | # Description : Parses network device configuration files 9 | # and checks to see if your devices are configured in the 10 | # same way. 11 | # 12 | # The Script is part of Nelkit (NetworkLore Toolkit) 13 | # http://networklore.com/nelkit/ 14 | # 15 | # Guidelines and updates: 16 | # http://networklore.com/compare-router-configs/ 17 | # 18 | # Feedback: Please send feedback: 19 | # http://networklore.com/contact/ 20 | # 21 | #========================================================== 22 | #========================================================== 23 | 24 | # Default settings 25 | RULESFILE="settings" 26 | WORKDIR="work" 27 | BASELINE="" 28 | 29 | SCRIPTVERSION="1.0" 30 | 31 | bFINDWORKDIR="false" 32 | bFINDBASELINE="false" 33 | bFINDDEVICELIST="false" 34 | 35 | #========================================================== 36 | # Define functions 37 | #========================================================== 38 | 39 | function check_distress { 40 | # Check to see if help has been requested 41 | if [ `echo "$1" | grep -E -c "^-h$|^help$|^--help$|^-help$"` -gt 0 ]; then 42 | display_help 43 | fi 44 | } 45 | 46 | function display_help { 47 | echo "" 48 | echo "nk-compare-configs.sh v.$SCRIPTVERSION" 49 | echo "" 50 | echo "Usage:" 51 | echo "./nk-compare-configs.sh" 52 | echo "./nk-compare-configs.sh rulesfile" 53 | echo "./nk-compare-configs.sh rulesfile configdir" 54 | echo "./nk-compare-configs.sh rulesfile configdir baselineconfig" 55 | echo "./nk-compare-configs.sh rulesfile configdir baselineconfig devlicelist" 56 | echo "" 57 | echo "Guidelines:" 58 | echo "You can run the script with 0-4 arguments. If you don't use any arguments" 59 | echo "the script will default to using a file called 'settings' in the current" 60 | echo "directory. The script will also default to looking in a directory called" 61 | echo "'work'. If you don't specify a baseline config file the script will choose" 62 | echo "the first file from the work directory. If you don't want the script to parse" 63 | echo "all of the files in the work directory you can point to a file containing" 64 | echo "a subset of your devices." 65 | echo "" 66 | echo "For more information and a howto guide which help you setup your settings" 67 | echo "file, visit:" 68 | echo "" 69 | echo "http://networklore.com/compare-router-configs/" 70 | echo "" 71 | exit 72 | } 73 | 74 | function match { 75 | MATCHSTRING="$1" 76 | RUN=1 77 | echo "" 78 | echo "--------------------------------------" 79 | echo "match" 80 | echo "string: $MATCHSTRING" 81 | echo "--------------------------------------" 82 | if [ "$bUSEBASELINE" = "true" ] ; then 83 | REFERENCE=`grep -E -- "$MATCHSTRING" $BASELINE` 84 | fi 85 | 86 | for DEVICE in $COLLECTION 87 | do 88 | if [ "$bUSEBASELINE" = "false" ] ; then 89 | if [ $RUN -eq 1 ]; then 90 | REFERENCE=`grep -E -- "$MATCHSTRING" $WORKDIR/$DEVICE` 91 | fi 92 | fi 93 | 94 | CURRENT=`grep -E -- "$MATCHSTRING" $WORKDIR/$DEVICE` 95 | 96 | RUN=`expr $RUN + 1` 97 | DIFF=`diff <(echo "$REFERENCE" ) <(echo "$CURRENT")` 98 | if [ `echo ${#DIFF}` -gt 0 ]; then 99 | echo "$DEVICE differs from baseline" 100 | fi 101 | done 102 | } 103 | 104 | function match_exclude { 105 | MATCHSTRING="$1" 106 | EXCLUDESTRING="$2" 107 | RUN=1 108 | 109 | echo "" 110 | echo "--------------------------------------" 111 | echo "match_exclude" 112 | echo "match: $MATCHSTRING" 113 | echo "exclude: $EXCLUDESTRING" 114 | echo "--------------------------------------" 115 | if [ "$bUSEBASELINE" = "true" ] ; then 116 | REFERENCE=`grep -E -- "$MATCHSTRING" $BASELINE | grep -E -v -- "$EXCLUDESTRING"` 117 | fi 118 | 119 | for DEVICE in $COLLECTION 120 | do 121 | if [ "$bUSEBASELINE" = "false" ] ; then 122 | if [ $RUN -eq 1 ]; then 123 | REFERENCE=`grep -E -- "$MATCHSTRING" $WORKDIR/$DEVICE | grep -E -v -- "$EXCLUDESTRING"` 124 | fi 125 | fi 126 | CURRENT=`grep -E -- "$MATCHSTRING" $WORKDIR/$DEVICE | grep -E -v -- "$EXCLUDESTRING"` 127 | 128 | RUN=`expr $RUN + 1` 129 | DIFF=`diff <(echo "$REFERENCE" ) <(echo "$CURRENT")` 130 | if [ `echo ${#DIFF}` -gt 0 ]; then 131 | echo "$DEVICE differs from baseline" 132 | fi 133 | done 134 | } 135 | 136 | function match_sort { 137 | MATCHSTRING="$1" 138 | RUN=1 139 | echo "" 140 | echo "--------------------------------------" 141 | echo "match_sort" 142 | echo "string: $MATCHSTRING" 143 | echo "--------------------------------------" 144 | if [ "$bUSEBASELINE" = "true" ] ; then 145 | REFERENCE=`grep -E -- "$MATCHSTRING" $BASELINE | sort` 146 | fi 147 | 148 | for DEVICE in $COLLECTION 149 | do 150 | if [ "$bUSEBASELINE" = "false" ] ; then 151 | if [ $RUN -eq 1 ]; then 152 | REFERENCE=`grep -E -- "$MATCHSTRING" $WORKDIR/$DEVICE | sort` 153 | fi 154 | fi 155 | CURRENT=`grep -E -- "$MATCHSTRING" $WORKDIR/$DEVICE | sort` 156 | 157 | RUN=`expr $RUN + 1` 158 | DIFF=`diff <(echo "$REFERENCE" ) <(echo "$CURRENT")` 159 | if [ `echo ${#DIFF}` -gt 0 ]; then 160 | echo "$DEVICE differs from baseline" 161 | fi 162 | done 163 | } 164 | 165 | function match_exclude_sort { 166 | MATCHSTRING="$1" 167 | EXCLUDESTRING="$2" 168 | RUN=1 169 | 170 | echo "" 171 | echo "--------------------------------------" 172 | echo "match_exclude_sort" 173 | echo "match: $MATCHSTRING" 174 | echo "exclude: $EXCLUDESTRING" 175 | echo "--------------------------------------" 176 | if [ "$bUSEBASELINE" = "true" ] ; then 177 | REFERENCE=`grep -E -- "$MATCHSTRING" $BASELINE | grep -E -v -- "$EXCLUDESTRING" | sort` 178 | fi 179 | 180 | for DEVICE in $COLLECTION 181 | do 182 | if [ "$bUSEBASELINE" = "false" ] ; then 183 | if [ $RUN -eq 1 ]; then 184 | REFERENCE=`grep -E -- "$MATCHSTRING" $WORKDIR/$DEVICE | grep -E -v -- "$EXCLUDESTRING" | sort` 185 | fi 186 | fi 187 | CURRENT=`grep -E -- "$MATCHSTRING" $WORKDIR/$DEVICE | grep -E -v -- "$EXCLUDESTRING" | sort` 188 | 189 | RUN=`expr $RUN + 1` 190 | DIFF=`diff <(echo "$REFERENCE" ) <(echo "$CURRENT")` 191 | if [ `echo ${#DIFF}` -gt 0 ]; then 192 | echo "$DEVICE differs from baseline" 193 | fi 194 | done 195 | } 196 | 197 | function start_end { 198 | BEGINSTRING="$1" 199 | ENDSTRING="$2" 200 | RUN=1 201 | 202 | echo "" 203 | echo "--------------------------------------" 204 | echo "start_end" 205 | echo "start: $BEGINSTRING" 206 | echo "end: $ENDSTRING" 207 | echo "--------------------------------------" 208 | if [ "$bUSEBASELINE" = "true" ] ; then 209 | REFERENCE=`sed -n "/$BEGINSTRING/,/$ENDSTRING/p" $BASELINE` 210 | if [ "${#REFERENCE}" = "0" ] ; then 211 | echo "No match found in reference" 212 | fi 213 | fi 214 | 215 | for DEVICE in $COLLECTION 216 | do 217 | if [ "$bUSEBASELINE" = "false" ] ; then 218 | if [ $RUN -eq 1 ]; then 219 | REFERENCE=`sed -n "/$BEGINSTRING/,/$ENDSTRING/p" $WORKDIR/$DEVICE` 220 | if [ "${#REFERENCE}" = "0" ] ; then 221 | echo "No match found in reference" 222 | fi 223 | fi 224 | fi 225 | 226 | CURRENT=`sed -n "/$BEGINSTRING/,/$ENDSTRING/p" $WORKDIR/$DEVICE` 227 | RUN=`expr $RUN + 1` 228 | DIFF=`diff <(echo "$REFERENCE" ) <(echo "$CURRENT")` 229 | if [ `echo ${#DIFF}` -gt 0 ]; then 230 | echo "$DEVICE differs from baseline" 231 | fi 232 | done 233 | } 234 | 235 | function start_end_exclude { 236 | BEGINSTRING="$1" 237 | ENDSTRING="$2" 238 | EXCLUDESTRING="$3" 239 | RUN=1 240 | 241 | echo "" 242 | echo "--------------------------------------" 243 | echo "start_end_exclude" 244 | echo "start: $BEGINSTRING" 245 | echo "end: $ENDSTRING" 246 | echo "exclude: $EXCLUDESTRING" 247 | echo "--------------------------------------" 248 | if [ "$bUSEBASELINE" = "true" ] ; then 249 | REFERENCE=`sed -n "/$BEGINSTRING/,/$ENDSTRING/p" $BASELINE | grep -E -v -- "$EXCLUDESTRING"` 250 | if [ "${#REFERENCE}" = "0" ] ; then 251 | echo "No match found in reference" 252 | fi 253 | fi 254 | 255 | for DEVICE in $COLLECTION 256 | do 257 | if [ "$bUSEBASELINE" = "false" ] ; then 258 | if [ $RUN -eq 1 ]; then 259 | REFERENCE=`sed -n "/$BEGINSTRING/,/$ENDSTRING/p" $WORKDIR/$DEVICE | grep -E -v -- "$EXCLUDESTRING"` 260 | if [ "${#REFERENCE}" = "0" ] ; then 261 | echo "No match found in reference" 262 | fi 263 | fi 264 | fi 265 | 266 | CURRENT=`sed -n "/$BEGINSTRING/,/$ENDSTRING/p" $WORKDIR/$DEVICE | grep -E -v -- "$EXCLUDESTRING"` 267 | RUN=`expr $RUN + 1` 268 | DIFF=`diff <(echo "$REFERENCE" ) <(echo "$CURRENT")` 269 | if [ `echo ${#DIFF}` -gt 0 ]; then 270 | echo "$DEVICE differs from baseline" 271 | fi 272 | done 273 | } 274 | 275 | function verify_parameters { 276 | 277 | # Check to see if the rulesfile is readable 278 | if [ ! -r "$RULESFILE" ] ; then 279 | echo "" 280 | echo "## ERROR ########################################" 281 | echo "Unable to read settings from: $RULESFILE" 282 | echo "#################################################" 283 | display_help 284 | fi 285 | 286 | # Check to see if the working directory should be read from the settings file 287 | if [ "$bFINDWORKDIR" = "true" ] ; then 288 | SETTINGSWORKDIR=`grep -- "^CONFIGDIR" "$RULESFILE" | cut -f 2 -d "=" | sed -r 's/"//g'` 289 | if [ `echo ${#SETTINGSWORKDIR}` -gt 0 ]; then 290 | WORKDIR="$SETTINGSWORKDIR" 291 | fi 292 | fi 293 | 294 | # Check to see if the working directory exists 295 | if [ ! -d "$WORKDIR" ]; then 296 | # Control will enter here if $DIRECTORY doesn't exist. 297 | echo "" 298 | echo "## ERROR ########################################" 299 | echo "Unable to read working directory: $WORKDIR" 300 | echo "#################################################" 301 | display_help 302 | fi 303 | 304 | # Check to see if the baseline file should be read from the settings file 305 | if [ "$bFINDBASELINE" = "true" ] ; then 306 | SETTINGSBASELINE=`grep -- "^BASECONFIG" $RULESFILE | cut -f 2 -d "=" | sed -r 's/"//g'` 307 | if [ `echo ${#SETTINGSBASELINE}` -gt 0 ]; then 308 | BASELINE="$SETTINGSBASELINE" 309 | fi 310 | fi 311 | 312 | if [ `echo ${#BASELINE}` -gt 0 ]; then 313 | bUSEBASELINE="true" 314 | if [ `echo $BASELINE | grep -c "/"` -eq 0 ]; then 315 | # A / is not included in the baseline setting, use baseline from workdir 316 | BASELINE="$WORKDIR/$BASELINE" 317 | fi 318 | if [ ! -r "$BASELINE" ] ; then 319 | echo "" 320 | echo "## ERROR ########################################" 321 | echo "Unable to read baseline file: $BASELINE" 322 | echo "#################################################" 323 | display_help 324 | fi 325 | 326 | else 327 | bUSEBASELINE="false" 328 | fi 329 | 330 | # Check to see if a devicelist should be read from the settings file 331 | if [ "$bFINDDEVICELIST" = "true" ] ; then 332 | SETTINGSDEVICELIST=`grep -- "^DEVICEFILE" "$RULESFILE" | cut -f 2 -d "=" | sed -r 's/"//g'` 333 | if [ `echo ${#SETTINGSDEVICELIST}` -gt 0 ]; then 334 | DEVICELIST="$SETTINGSDEVICELIST" 335 | fi 336 | fi 337 | 338 | if [ `echo ${#DEVICELIST}` -gt 0 ]; then 339 | # Read from device list 340 | if [ ! -r "$DEVICELIST" ] ; then 341 | echo "" 342 | echo "## ERROR ########################################" 343 | echo "Unable to read devicelist: $DEVICELIST" 344 | echo "#################################################" 345 | display_help 346 | fi 347 | # Use a specific subset of working directory 348 | COLLECTION=`cat $DEVICELIST` 349 | else 350 | # Use all of the devices in the workdir 351 | COLLECTION=`ls -1 $WORKDIR` 352 | fi 353 | 354 | #Verify that we can read our collection 355 | for FILE in $COLLECTION 356 | do 357 | 358 | if [ ! -r "$WORKDIR/$FILE" ] ; then 359 | echo "## ERROR ########################################" 360 | echo "Unable to read the file: $WORKDIR/$FILE" 361 | echo "#################################################" 362 | display_help 363 | fi 364 | done 365 | 366 | } 367 | 368 | #========================================================== 369 | # Main Script 370 | #========================================================== 371 | 372 | # Parse script arguments 373 | 374 | if [ $# -gt 4 ] ; then 375 | echo "You have enterned too many arguments" 376 | display_help 377 | elif [ $# -eq 4 ] ; then 378 | RULESFILE="$1" 379 | WORKDIR="$2" 380 | BASELINE="$3" 381 | DEVICELIST="$4" 382 | elif [ $# -eq 3 ] ; then 383 | RULESFILE="$1" 384 | WORKDIR="$2" 385 | BASELINE="$3" 386 | bFINDDEVICELIST="true" 387 | elif [ $# -eq 2 ] ; then 388 | RULESFILE="$1" 389 | WORKDIR="$2" 390 | bFINDBASELINE="true" 391 | bFINDDEVICELIST="true" 392 | elif [ $# -eq 1 ] ; then 393 | RULESFILE="$1" 394 | bFINDWORKDIR="true" 395 | bFINDBASELINE="true" 396 | bFINDDEVICELIST="true" 397 | elif [ $# -eq 0 ] ; then 398 | bFINDWORKDIR="true" 399 | bFINDBASELINE="true" 400 | bFINDDEVICELIST="true" 401 | fi 402 | 403 | check_distress $RULESFILE 404 | check_distress $WORKDIR 405 | check_distress $BASELINE 406 | check_distress $DEVICELIST 407 | 408 | verify_parameters 409 | 410 | . $RULESFILE 411 | 412 | -------------------------------------------------------------------------------- /compare-bash/settings: -------------------------------------------------------------------------------- 1 | ################################################# 2 | # For a guide to help you configure your 3 | # settings file please visit: 4 | # http://networklore.com/compare-router-configs/ 5 | ################################################# 6 | 7 | 8 | # Match all lines beginning with "ntp" 9 | match "^ntp" 10 | 11 | # Match all lines beginning with "snmp-server", but exclude "location" 12 | match_exclude "^snmp-server" "location" 13 | 14 | # Match all lines containing "logging" 15 | match "logging" 16 | 17 | # Match from a line starting with "line con" until a line starting 18 | # with "!" 19 | start_end "^line con" "^!" 20 | 21 | # Match from a line starting with "interface GigabitEthernet0/0" 22 | # (note the escaped "/" character) until a line starting with "!" 23 | # but ignore the "ip address" line or any line containing "flow" 24 | start_end_exclude "^interface GigabitEthernet0\/0" "^!" "ip address|flow" 25 | 26 | # Match from a line starting with "ip access-list extended ACL-INTERNET-V6" 27 | # match until a line starts with "!" or "i" (as in a new access-list), 28 | # but also exclude lines starting with either "!" or "i" 29 | start_end_exclude "^ip access-list extended ACL-INTERNET-V6" "^[!i]" "^[!i]" 30 | 31 | # Match lines starting with "service" or "no service" 32 | match_sort "^service|^no service" 33 | 34 | # Match lines starting with "ip inspect name", but don't care about the 35 | # order the commands appear in 36 | match_sort "^ip inspect name" 37 | 38 | # Match lines starting with "ip inspect name", but don't care about the 39 | # order the commands appear in. Also exclude lines containing DMZFW 40 | match_exclude_sort "^ip inspect name" "DMZFW" 41 | 42 | -------------------------------------------------------------------------------- /lib/nelkit/__init__.py: -------------------------------------------------------------------------------- 1 | """Nelkit - Networklore Toolkit.""" 2 | 3 | __author__ = 'Patrick Ogenstad' 4 | __version__ = '0.0.5' 5 | -------------------------------------------------------------------------------- /lib/nelkit/args/__init__.py: -------------------------------------------------------------------------------- 1 | """Argument module.""" 2 | -------------------------------------------------------------------------------- /lib/nelkit/args/base.py: -------------------------------------------------------------------------------- 1 | """Base argument module.""" 2 | from argparse import ArgumentParser, RawTextHelpFormatter 3 | 4 | 5 | # class NkArgumentParser(ArgumentParser): 6 | # pass 7 | 8 | 9 | class HelpText(object): 10 | """Nelkit specific helptext.""" 11 | 12 | def __init__(self, description, epilog): 13 | """Nelkit specific helptext.""" 14 | desc_prefix = '#' * 75 15 | desc_prefix += '\n' 16 | desc_suffix = '#' * 75 17 | self.description = desc_prefix + description + '\n' + desc_suffix 18 | epilog_suffix = '#' * 75 19 | epilog_suffix += '\n' 20 | epilog_suffix += 'This tool is part of Nelkit:\n' 21 | epilog_suffix += 'https://networklore.com/nelkit\n' 22 | epilog_suffix += '\n' 23 | self.epilog = epilog_suffix + epilog 24 | 25 | 26 | class BaseArgs(object): 27 | """Nelkit base argument class.""" 28 | 29 | def __init__(self, description, epilog=''): 30 | """Nelkit base argument class.""" 31 | helptext = HelpText(description, epilog) 32 | self.parser = ArgumentParser( 33 | description=helptext.description, 34 | epilog=helptext.epilog, 35 | formatter_class=RawTextHelpFormatter) 36 | 37 | self.parser.add_argument( 38 | '-V', 39 | help='Show version', 40 | action='store_true') 41 | self.parser.add_argument( 42 | '-O', 43 | help='Output format', 44 | choices=['standard', 'with_status'], 45 | default='standard' 46 | ) 47 | 48 | self._add_local_args() 49 | 50 | def _add_local_args(self): 51 | pass 52 | -------------------------------------------------------------------------------- /lib/nelkit/args/snmp.py: -------------------------------------------------------------------------------- 1 | """Module to handle snmp arguments for cli tools.""" 2 | from nelkit.args.base import BaseArgs 3 | 4 | 5 | class SnmpArgs(BaseArgs): 6 | """Argument class for snmp tools.""" 7 | 8 | def __init__(self, description, epilog=''): 9 | """Argument class for snmp tools.""" 10 | super(SnmpArgs, self).__init__(description, epilog) 11 | 12 | def _add_local_args(self): 13 | self.parser.add_argument('-H', help='Target host', required=True) 14 | self.parser.add_argument( 15 | '-p', help="Port number (default: 161)", default=161) 16 | self.parser.add_argument( 17 | '-P', help="SNMP protocol version", choices=['2c', '3'], required=True) 18 | self.parser.add_argument('-C', help="SNMP Community string") 19 | self.parser.add_argument( 20 | '-L', help="SNMPv3 Security level", 21 | choices=['authNoPriv', 'authPriv']) 22 | self.parser.add_argument( 23 | '-a', help="SNMPv3 authentiction protocol", 24 | choices=['MD5', 'SHA']) 25 | self.parser.add_argument( 26 | '-x', help="SNMPv3 privacy protocol", 27 | choices=['DES', 'AES']) 28 | self.parser.add_argument('-U', help="SNMPv3 username") 29 | self.parser.add_argument('-A', help="SNMPv3 authentication password") 30 | self.parser.add_argument('-X', help="SNMPv3 privacy password") 31 | -------------------------------------------------------------------------------- /lib/nelkit/cli/__init__.py: -------------------------------------------------------------------------------- 1 | """Nelkit command line tools.""" 2 | -------------------------------------------------------------------------------- /lib/nelkit/cli/compare_configs.py: -------------------------------------------------------------------------------- 1 | """nk-compare-configs.""" 2 | from nelkit.args.base import BaseArgs 3 | from nelkit.globals import NelkitGlobals 4 | from nelkit.modules.compare_configs.settings import CompareConfigs 5 | 6 | description = 'Compare configurations against a baseline' 7 | 8 | 9 | def main(): 10 | """Launch nk-compare-configs.""" 11 | NelkitGlobals(FRIENDLY_EXCEPTION=True) 12 | 13 | argparser = BaseArgs(description) 14 | argparser.parser.add_argument( 15 | '-c', 16 | help='Configuration file', 17 | type=str, 18 | required=True) 19 | args = argparser.parser.parse_args() 20 | cc = CompareConfigs(settings_file=args.c) 21 | cc.output_diff() 22 | 23 | 24 | if __name__ == "__main__": 25 | main() 26 | -------------------------------------------------------------------------------- /lib/nelkit/cli/snmp_deviceinfo.py: -------------------------------------------------------------------------------- 1 | """nk-snmp-deviceinfo - command line tool.""" 2 | from nelkit.args.snmp import SnmpArgs 3 | from nelkit.snmp.handler import NelkitSnmp 4 | from nelsnmp.hostinfo.device import HostInfo 5 | 6 | description = 'Collects Device info using SNMP' 7 | 8 | 9 | def main(): 10 | """run nk-snmp-deviceinfo.""" 11 | argparser = SnmpArgs(description) 12 | args = argparser.parser.parse_args() 13 | snmp = NelkitSnmp(args) 14 | hostinfo = HostInfo(snmp) 15 | try: 16 | hostinfo.get_all() 17 | print('OS: %s' % hostinfo.os) 18 | print('Version: %s' % hostinfo.version) 19 | print('Vendor: %s' % hostinfo.vendor) 20 | print('Description: %s' % hostinfo.description) 21 | except Exception as e: 22 | print('ERROR: {0}'.format(e)) 23 | 24 | 25 | if __name__ == "__main__": 26 | main() 27 | -------------------------------------------------------------------------------- /lib/nelkit/exceptions.py: -------------------------------------------------------------------------------- 1 | """This module contains exceptions available to nelkit.""" 2 | 3 | import sys 4 | from nelkit.globals import NelkitGlobals 5 | 6 | 7 | class NelkitException(Exception): 8 | """Base nelkit exception class. 9 | 10 | By default an exception will be raised, this can be overwritten using 11 | nelkit.globals.NelkitGlobals so that the error messages are printed out 12 | to the screen instead. This is done from the cli tools. 13 | """ 14 | 15 | def __init__(self, message): 16 | """Base nelkit exception class.""" 17 | if NelkitGlobals.FRIENDLY_EXCEPTION: 18 | print(message) 19 | sys.exit() 20 | else: 21 | super(NelkitException, self).__init__(message) 22 | 23 | 24 | class ArgumentError(NelkitException): 25 | """Raised when passing invalid arguments using cli tools.""" 26 | 27 | pass 28 | 29 | 30 | class FileNotFound(NelkitException): 31 | """Raised when trying to open a file which doesn't exist.""" 32 | 33 | pass 34 | 35 | 36 | class ParsingError(NelkitException): 37 | """Raised when trying to parse a file with invalid formatting.""" 38 | 39 | pass 40 | -------------------------------------------------------------------------------- /lib/nelkit/globals.py: -------------------------------------------------------------------------------- 1 | """This module contains the NelkitGlobals class used to override standard settings.""" 2 | 3 | 4 | class NelkitGlobals(object): 5 | """Nelkit global class to override standard settings.""" 6 | 7 | FRIENDLY_EXCEPTION = None 8 | 9 | def __init__(self, **kwargs): 10 | """Override standard settings. 11 | 12 | :param FRIENDLY_EXCEPTION: (optional) Boolean value to control if errors are to be printed or raised. 13 | """ 14 | for key in kwargs: 15 | if key == 'FRIENDLY_EXCEPTION': 16 | NelkitGlobals.FRIENDLY_EXCEPTION = kwargs[key] 17 | -------------------------------------------------------------------------------- /lib/nelkit/modules/__init__.py: -------------------------------------------------------------------------------- 1 | """nelkit.modules.""" 2 | -------------------------------------------------------------------------------- /lib/nelkit/modules/compare_configs/__init__.py: -------------------------------------------------------------------------------- 1 | """nelkit.modules.compare_configs.""" 2 | -------------------------------------------------------------------------------- /lib/nelkit/modules/compare_configs/settings.py: -------------------------------------------------------------------------------- 1 | """Module to compare different configurations files.""" 2 | import difflib 3 | import itertools 4 | import os 5 | import re 6 | from glob import glob 7 | from nelkit.exceptions import FileNotFound, NelkitException 8 | from nelkit.parsing.yaml.loader import YamlLoader 9 | 10 | 11 | class CompareConfigs: 12 | """Compare configs class, used in nk-compare-configs.""" 13 | 14 | def __init__(self, settings_file=None, config_dir=None, baseline=None): 15 | """Compare configs class, used in nk-compare-configs.""" 16 | self._baseline = baseline 17 | self._settings = settings_file 18 | self._config_dir = config_dir 19 | self.rules = {} 20 | self._between = {} 21 | self._num_rules = 0 22 | self._diff = {} 23 | self._parse_settings() 24 | 25 | if not self._baseline: 26 | if len(self._config_files) > 0: 27 | self._baseline = self._config_files[0] 28 | else: 29 | raise FileNotFound('No config files found') 30 | else: 31 | if '/' not in self._baseline: 32 | self._baseline = self._config_dir + '/' + self._baseline 33 | 34 | self._parse_config_files() 35 | self._parse_sort_rules() 36 | self._compare_configs() 37 | 38 | def _compare_configs(self): 39 | for host in self._config_files: 40 | for rule in self._baseline_matches: 41 | if self._baseline_matches[rule] != self._matches[host][rule]: 42 | node = host.split(os.path.sep)[-1] 43 | diff = difflib.unified_diff( 44 | self._baseline_matches[rule], 45 | self._matches[host][rule], 46 | fromfile='baseline', 47 | tofile=node) 48 | diff = os.linesep.join([x for x in diff]) 49 | if host not in self._diff.keys(): 50 | self._diff[host] = {} 51 | 52 | self._diff[host][rule] = diff 53 | 54 | def _parse_between_rule(self, rule): 55 | self._num_rules += 1 56 | if 'start' not in rule.keys(): 57 | raise NelkitException('start key missing in between rule') 58 | if not isinstance(rule['start'], str): 59 | raise NelkitException('"start" under between rule has the wrong format') 60 | match = {} 61 | match['rule_type'] = 'between' 62 | match['start'] = rule['start'] 63 | match['start_re'] = re.compile(rule['start']) 64 | match['end'] = rule.get('end') 65 | match['sort'] = rule.get('sort') 66 | match['until_not'] = rule.get('until_not') 67 | if match['end']: 68 | match['end_re'] = re.compile(match['end']) 69 | if match['until_not']: 70 | match['until_not_re'] = re.compile(match['until_not']) 71 | match['description'] = rule.get('description') 72 | 73 | if match['end'] and match['until_not']: 74 | raise NelkitException('"between" rule can not have both end and until_not') 75 | elif not match['end'] and not match['until_not']: 76 | raise NelkitException('"between" rule must have end or until_not') 77 | 78 | if 'exclude' in rule.keys(): 79 | match['exclude'] = rule['exclude'] 80 | else: 81 | match['exclude'] = None 82 | if match['exclude']: 83 | match['exclude_re'] = re.compile(match['exclude']) 84 | 85 | self.rules[self._num_rules] = match 86 | 87 | def _parse_configs_dir(self, config_setting, data): 88 | 89 | if config_setting: 90 | config_dir = config_setting 91 | else: 92 | if 'configs' in data.keys(): 93 | config_dir = data['configs'] 94 | else: 95 | raise NelkitException('config key not found in file') 96 | self._config_dir = config_dir 97 | 98 | if not isinstance(config_dir, list): 99 | config_dir = [config_dir] 100 | 101 | config_files = [] 102 | for cur_path in config_dir: 103 | if not os.path.isdir(cur_path): 104 | raise NelkitException('%s is not a valid config directory' % cur_path) 105 | if cur_path[-1] != os.path.sep: 106 | cur_path += os.path.sep 107 | config_files.append(glob('%s*' % cur_path)) 108 | self._config_files = list(itertools.chain.from_iterable(config_files)) 109 | 110 | def _parse_config_files(self): 111 | self._matches = {} 112 | for config in self._config_files: 113 | if config not in self._matches.keys(): 114 | self._matches[config] = {} 115 | 116 | with open(config) as f: 117 | for line in f: 118 | for rule in self.rules: 119 | if rule not in self._matches[config].keys(): 120 | self._matches[config][rule] = [] 121 | if self.rules[rule]['rule_type'] == 'match': 122 | self._run_match_rule(rule, config, line) 123 | elif self.rules[rule]['rule_type'] == 'between': 124 | self._run_between_rule(rule, config, line) 125 | 126 | def _parse_match_rule(self, rule): 127 | self._num_rules += 1 128 | if 'string' not in rule.keys(): 129 | raise NelkitException('string missing in match rule') 130 | if not isinstance(rule['string'], str): 131 | raise NelkitException('"string" under match rule has the wrong format') 132 | match = {} 133 | match['rule_type'] = 'match' 134 | match['description'] = rule.get('description') 135 | match['string'] = rule['string'] 136 | match['string_re'] = re.compile(rule['string']) 137 | match['sort'] = rule.get('sort') 138 | if 'exclude' in rule.keys(): 139 | match['exclude'] = rule['exclude'] 140 | else: 141 | match['exclude'] = None 142 | self.rules[self._num_rules] = match 143 | 144 | def _parse_settings(self): 145 | l = YamlLoader(filename=self._settings) 146 | data = l.data 147 | self._parse_configs_dir(self._config_dir, data) 148 | 149 | if not self._baseline: 150 | self._baseline = data.get('baseline') 151 | 152 | if 'rules' not in data.keys(): 153 | raise NelkitException('rules key not found in file') 154 | rules = data['rules'] 155 | 156 | if not isinstance(rules, list): 157 | raise NelkitException('rules has to be a list') 158 | 159 | for rule in rules: 160 | if not isinstance(rule, dict): 161 | raise NelkitException('rule not a dict!!!!') 162 | 163 | for criteria in rule: 164 | if not isinstance(rule[criteria], dict): 165 | raise NelkitException('NOoOooooo') 166 | if criteria == 'between': 167 | self._parse_between_rule(rule[criteria]) 168 | elif criteria == 'match': 169 | self._parse_match_rule(rule[criteria]) 170 | else: 171 | raise NelkitException('%s is not a valid rule type' % criteria) 172 | 173 | def _parse_sort_rules(self): 174 | 175 | for host in self._matches: 176 | for rule in self._matches[host]: 177 | if self.rules[rule]['sort']: 178 | self._matches[host][rule] = sorted(self._matches[host][rule]) 179 | 180 | if self._baseline not in self._matches.keys(): 181 | raise NelkitException('Unable to find baseline') 182 | else: 183 | self._baseline_matches = self._matches[self._baseline] 184 | 185 | def _run_between_rule(self, rule, config, line): 186 | if config not in self._between.keys(): 187 | self._between[config] = {} 188 | if rule not in self._between[config].keys(): 189 | self._between[config][rule] = {} 190 | self._between[config][rule]['in'] = False 191 | if self.rules[rule]['end']: 192 | if self._between[config][rule]['in']: 193 | if self.rules[rule]['end_re'].match(line): 194 | self._between[config][rule]['in'] = False 195 | if self.rules[rule]['exclude']: 196 | if self.rules[rule]['exclude_re'].match(line): 197 | return 198 | self._matches[config][rule].append(line.rstrip()) 199 | 200 | if self.rules[rule]['until_not']: 201 | if self._between[config][rule]['in']: 202 | if self.rules[rule]['until_not_re'].match(line): 203 | if self.rules[rule]['exclude']: 204 | if self.rules[rule]['exclude_re'].match(line): 205 | return 206 | self._matches[config][rule].append(line.rstrip()) 207 | else: 208 | self._between[config][rule]['in'] = False 209 | 210 | if self.rules[rule]['start_re'].match(line): 211 | self._between[config][rule]['in'] = True 212 | if self.rules[rule]['exclude']: 213 | exclude = re.compile(self.rules[rule]['exclude']) 214 | if exclude.match(line): 215 | return 216 | self._matches[config][rule].append(line.rstrip()) 217 | 218 | def _run_match_rule(self, rule, config, line): 219 | if self.rules[rule]['string_re'].match(line): 220 | if self.rules[rule]['exclude']: 221 | exclude = re.compile(self.rules[rule]['exclude']) 222 | if exclude.match(line): 223 | return 224 | self._matches[config][rule].append(line.rstrip()) 225 | 226 | def output_diff(self): 227 | """Print output with diffs.""" 228 | for host in sorted(self._diff.keys()): 229 | node = host.split(os.path.sep)[-1] 230 | print('#########################') 231 | print('# %s' % node) 232 | print('#########################') 233 | for rule in self._diff[host]: 234 | print(self._diff[host][rule]) 235 | print('#########################') 236 | print('') 237 | -------------------------------------------------------------------------------- /lib/nelkit/parsing/__init__.py: -------------------------------------------------------------------------------- 1 | """nelkit.parsing.""" 2 | -------------------------------------------------------------------------------- /lib/nelkit/parsing/yaml/__init__.py: -------------------------------------------------------------------------------- 1 | """nelkit.parsing.yaml.""" 2 | -------------------------------------------------------------------------------- /lib/nelkit/parsing/yaml/loader.py: -------------------------------------------------------------------------------- 1 | """Load yaml files.""" 2 | import os 3 | import yaml 4 | from nelkit.exceptions import FileNotFound, ParsingError 5 | 6 | 7 | class YamlLoader: 8 | """Load yaml files.""" 9 | 10 | def __init__(self, filename=None): 11 | """Load yaml files. 12 | 13 | :param filename: The yaml file to load 14 | 15 | Usage:: 16 | >>> from nelkit.parsing.yaml.loader import YamlLoader 17 | >>> y = YamlLoader('my_file.yml') 18 | >>> content = y.data 19 | """ 20 | self._filename = filename 21 | self._load() 22 | 23 | def _load(self): 24 | if not os.path.isfile(self._filename): 25 | raise FileNotFound('Unable to read: %s' % self._filename) 26 | try: 27 | with open(self._filename) as f: 28 | self.data = yaml.load(f.read()) 29 | except: 30 | raise ParsingError('%s is not a valid yaml file' % self._filename) 31 | -------------------------------------------------------------------------------- /lib/nelkit/snmp/__init__.py: -------------------------------------------------------------------------------- 1 | """nelkit.snmp.""" 2 | -------------------------------------------------------------------------------- /lib/nelkit/snmp/handler.py: -------------------------------------------------------------------------------- 1 | """Create an SNMP handler using nelsnmp.""" 2 | from nelkit.exceptions import ArgumentError 3 | from nelsnmp.snmp import cmdgen, SnmpHandler 4 | 5 | 6 | class NelkitSnmp(SnmpHandler): 7 | """Nelkit version of SnmpHandler from nelsnmp.""" 8 | 9 | def __init__(self, args): 10 | """Return a nelsnmp SnmpHandler.""" 11 | self._verify_snmp_arguments(args) 12 | self._set_snmp_parameters(args) 13 | 14 | def _set_snmp_parameters(self, args): 15 | self.version = args.P 16 | if args.P == "2c": 17 | self.snmp_auth = cmdgen.CommunityData(args.C) 18 | 19 | elif args.P == "3": 20 | self.username = args.U 21 | if args.a == "SHA": 22 | self.integrity = cmdgen.usmHMACSHAAuthProtocol 23 | elif args.a == "MD5": 24 | self.integrity = cmdgen.usmHMACMD5AuthProtocol 25 | 26 | if args.x == "AES": 27 | self.privacy = cmdgen.usmAesCfb128Protocol 28 | elif args.x == "DES": 29 | self.privacy = cmdgen.usmDESPrivProtocol 30 | 31 | self.authkey = args.A 32 | 33 | if args.L == "authPriv": 34 | self.privkey = args.X 35 | self.snmp_auth = cmdgen.UsmUserData( 36 | args.U, 37 | authKey=args.A, 38 | authProtocol=self.integrity, 39 | privKey=args.X, 40 | privProtocol=self.privacy) 41 | else: 42 | self.snmp_auth = cmdgen.UsmUserData( 43 | args.U, 44 | authKey=args.A, 45 | authProtocol=self.integrity) 46 | 47 | self.host = args.H 48 | self.port = int(args.p) 49 | self.timeout = 1 50 | self.retries = 5 51 | 52 | def _verify_snmp_arguments(self, args): 53 | if args.P == "2c" and args.C is None: 54 | ArgumentError('Specify community when using SNMP 2c') 55 | if args.P == "3" and args.U is None: 56 | ArgumentError('Specify username when using SNMP 3') 57 | if args.P == "3" and args.L is None: 58 | ArgumentError('Specify security level when using SNMP 3') 59 | if args.L == "authNoPriv" and args.a is None: 60 | ArgumentError('Specify authentication protocol when using authNoPriv') 61 | if args.L == "authNoPriv" and args.A is None: 62 | ArgumentError('Specify authentication password when using authNoPriv') 63 | if args.L == "authPriv" and args.a is None: 64 | ArgumentError('Specify authentication protocol when using authPriv') 65 | if args.L == "authPriv" and args.A is None: 66 | ArgumentError('Specify authentication password when using authPriv') 67 | if args.L == "authPriv" and args.x is None: 68 | ArgumentError('Specify privacy protocol when using authPriv') 69 | if args.L == "authPriv" and args.X is None: 70 | ArgumentError('Specify privacy password when using authPriv') 71 | -------------------------------------------------------------------------------- /pylama.ini: -------------------------------------------------------------------------------- 1 | [pylama] 2 | linters = mccabe,pep257,pep8,pyflakes 3 | ignore = D203, 4 | skip = .tox/* 5 | 6 | [pylama:pep8] 7 | max_line_length = 110 8 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | """setup.py.""" 2 | import re 3 | 4 | from codecs import open 5 | from setuptools import setup, find_packages 6 | 7 | version = '' 8 | with open('lib/nelkit/__init__.py', 'r') as fd: 9 | version = re.search(r'^__version__\s*=\s*[\'"]([^\'"]*)[\'"]', 10 | fd.read(), re.MULTILINE).group(1) 11 | 12 | if not version: 13 | raise RuntimeError('Cannot find version information') 14 | 15 | with open('README.rst', 'r', 'utf-8') as f: 16 | readme = f.read() 17 | 18 | with open('HISTORY.rst', 'r', 'utf-8') as f: 19 | history = f.read() 20 | 21 | long_description = readme + '\n\n' + history 22 | 23 | console_scripts = [ 24 | 'nk-compare-configs=nelkit.cli.compare_configs:main', 25 | 'nk-snmp-deviceinfo=nelkit.cli.snmp_deviceinfo:main' 26 | ] 27 | 28 | config = { 29 | 'name': 'nelkit', 30 | 'package_dir': {'': 'lib'}, 31 | 'packages': find_packages('lib'), 32 | 'entry_points': {'console_scripts': console_scripts}, 33 | 'version': version, 34 | 'description': 'A Toolkit for network engineers', 35 | 'long_description': long_description, 36 | 'author': 'Patrick Ogenstad', 37 | 'author_email': 'patrick@ogenstad.com', 38 | 'license': 'Apache', 39 | 'url': 'https://networklore.com/nelkit/', 40 | 'install_requires': ['argparse', 'nelsnmp >= 0.2.7', 'PyYAML'], 41 | 'classifiers': ['Development Status :: 4 - Beta', 42 | 'Intended Audience :: Developers', 43 | 'Intended Audience :: System Administrators'] 44 | } 45 | 46 | setup(**config) 47 | -------------------------------------------------------------------------------- /subscribe.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |
6 | 7 | 8 | 9 |47 | 48 | The Networklore Newsletter can help you What I have to offer 54 |
77 | What I want you to do 78 | 79 |
80 |
89 |
90 |
What I Guarantee 197 |
|
222 |
231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | -------------------------------------------------------------------------------- /templates/network-device-template.numbers/Index.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/networklore/nelkit/17472c4e00e0cf226a2ba48c140d93adcaca024a/templates/network-device-template.numbers/Index.zip -------------------------------------------------------------------------------- /templates/network-device-template.numbers/Metadata/BuildVersionHistory.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 |