├── .gitignore ├── README.md ├── Vagrantfile ├── automate-ssh-with-netmiko ├── collect-cdp-information.py ├── result.html └── show_cdp_neighbor_detail.textfsm ├── cisco-ios-config-parameter-extraction ├── ciscoconfiparse_example.py ├── example_config.txt └── regular_expression_example.py ├── cisco-nx-api-example └── interface-description-cleaner.py ├── config-generator-with-custom-filters ├── config-generator-with-custom-filter.py ├── ip-interface-config.jinja2 └── parameters.json ├── config-generator-with-python-and-jinja2 ├── csv_based_config_generator.py ├── json_based_config_generator.py ├── parameters.csv ├── parameters.json ├── switch.j2 └── switch_with_vlans.j2 ├── create-hsrp-interface-configuration ├── cisco_ios_vlans.txt └── create-hsrp-interface-configuration.py ├── json-code-example ├── load_json_data_example.py ├── validate_json_data_example.py └── view_json_data_example.py ├── jupyter-config ├── 10-nb-import-hook.py ├── about.ipynb ├── jupyter-notebook.upstart └── jupyter_notebook_config.py ├── migrate-static-arp-entries ├── cisco_static_arp_configuration.txt └── create-migration-templates.py ├── networkconfgen └── networkconfgen_example.ipynb ├── notebooks ├── Introduction to the ipaddress module with IPv4.ipynb ├── Introduction to the ipaddress module with IPv6.ipynb ├── __init__.py ├── import_test.ipynb └── pandas │ ├── DataFrame_Basics.ipynb │ ├── Merge DataFrames in Pandas.ipynb │ └── example_workbook.xlsx ├── parse-show-inventory-with-textfsm ├── parse_show_inventory.py ├── show_inventory.txt └── show_inventory_multiple.textfsm ├── requirements.txt ├── setup.yaml └── split_cli_output └── split_cli_ouput.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### VirtualEnv template 3 | # Virtualenv 4 | # http://iamzed.com/2009/05/07/a-primer-on-virtualenv/ 5 | .Python 6 | [Bb]in 7 | [Ii]nclude 8 | [Ll]ib 9 | [Ss]cripts 10 | pyvenv.cfg 11 | pip-selfcheck.json 12 | 13 | 14 | ### JetBrains template 15 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion 16 | 17 | *.iml 18 | 19 | ## Directory-based project format: 20 | .idea/ 21 | # if you remove the above rule, at least ignore the following: 22 | 23 | # User-specific stuff: 24 | # .idea/workspace.xml 25 | # .idea/tasks.xml 26 | # .idea/dictionaries 27 | 28 | # Sensitive or high-churn files: 29 | # .idea/dataSources.ids 30 | # .idea/dataSources.xml 31 | # .idea/sqlDataSources.xml 32 | # .idea/dynamic.xml 33 | # .idea/uiDesigner.xml 34 | 35 | # Gradle: 36 | # .idea/gradle.xml 37 | # .idea/libraries 38 | 39 | # Mongo Explorer plugin: 40 | # .idea/mongoSettings.xml 41 | 42 | ## File-based project format: 43 | *.ipr 44 | *.iws 45 | 46 | ## Plugin-specific files: 47 | 48 | # IntelliJ 49 | /out/ 50 | 51 | # mpeltonen/sbt-idea plugin 52 | .idea_modules/ 53 | 54 | # JIRA plugin 55 | atlassian-ide-plugin.xml 56 | 57 | # Crashlytics plugin (for Android Studio and IntelliJ) 58 | com_crashlytics_export_strings.xml 59 | crashlytics.properties 60 | crashlytics-build.properties 61 | 62 | 63 | ### Python template 64 | # Byte-compiled / optimized / DLL files 65 | __pycache__/ 66 | *.py[cod] 67 | *$py.class 68 | 69 | # C extensions 70 | *.so 71 | 72 | # Distribution / packaging 73 | .Python 74 | env/ 75 | build/ 76 | develop-eggs/ 77 | dist/ 78 | downloads/ 79 | eggs/ 80 | .eggs/ 81 | lib/ 82 | lib64/ 83 | parts/ 84 | sdist/ 85 | var/ 86 | *.egg-info/ 87 | .installed.cfg 88 | *.egg 89 | 90 | # PyInstaller 91 | # Usually these files are written by a python script from a template 92 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 93 | *.manifest 94 | *.spec 95 | 96 | # Installer logs 97 | pip-log.txt 98 | pip-delete-this-directory.txt 99 | 100 | # Unit test / coverage reports 101 | htmlcov/ 102 | .tox/ 103 | .coverage 104 | .coverage.* 105 | .cache 106 | nosetests.xml 107 | coverage.xml 108 | *,cover 109 | 110 | # Translations 111 | *.mo 112 | *.pot 113 | 114 | # Django stuff: 115 | *.log 116 | 117 | # Sphinx documentation 118 | docs/_build/ 119 | 120 | # PyBuilder 121 | target/ 122 | 123 | outfile.csv 124 | _* 125 | !__init__.py 126 | /**/.ipynb_checkpoints 127 | .vagrant 128 | automate-ssh-with-netmiko/data.js 129 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # python script examples 2 | 3 | This repository contains my python script examples that focuses on use cases for Network Engineers. They are explained 4 | in more detail in the associated Blog posts at the [Coding Networker Blog](https://codingnetworker.com). 5 | 6 | The following examples are included in this repository: 7 | 8 | * [Parse CLI outputs with TextFSM](https://codingnetworker.com/2015/08/parse-cli-outputs-textfsm/) - This post describes how to parse CLI outputs using TextFSM. Within the an example, I work with multiple "show inventory" commands from a Cisco IOS device in a single text file. 9 | * [Configuration generator with python and Jinja2](https://codingnetworker.com/2015/09/configuration-generator-with-python-and-jinja2/) - This post describes, how to build a simple configuration generator using python and Jinja2. I'll explain it using CSV and JSON based parameter files. 10 | * [Cisco NX-API on Nexus 5500](https://codingnetworker.com/2015/09/cisco-nx-api-nexus-5500/) - This post gives you a short introduction to the Cisco NX-API on Nexus 5500 with NX-OS 7.2 11 | * [Custom filters for a Jinja2 based config generator](https://codingnetworker.com/2015/10/custom-filters-jinja2-config-generator/) - In this post, I'll explain how to include custom filters in Jinja2 and how to use them within the configuration templates 12 | * [Implement HSRP using ciscoconfparse](https://codingnetworker.com/2015/10/implement-hsrp-using-ciscoconfparse/) - This post describes an example to create a configuration for HSRP based on an existing Cisco IOS configuration using the ciscoconfparse module 13 | * **JSON data structure** 14 | 15 | * [python dictionaries and JSON (crash course)](https://codingnetworker.com/2015/10/python-dictionaries-json-crash-course/) - Just a quick crash course about the use of python dictionaries and the JSON data format 16 | * [Validating JSON data using cerberus](https://codingnetworker.com/2016/03/validate-json-data-using-cerberus/) - In this post, I'll look at a way to verify JSON data using cerberus 17 | 18 | * [HTTP calls using the python requests library](https://codingnetworker.com/2015/10/http-calls-using-python-requests-library/) - How to use the requests library in python based on the example code from the Cisco NX-API post ("interface description cleaner") 19 | * [Reconfigure static ARP entries using ciscoconfparse](https://codingnetworker.com/2015/11/reconfigure-static-arp-entries-ciscoconfparse/) - This post describes how to parse an existing configuration and reconfigure it using the example of static ARP entries (from Cisco VSS to Cisco vPC) 20 | * [Introduction to the python ipaddress module](https://codingnetworker.com/2015/12/introduction-python-ipaddress-module/) - quick introduction to the python ipaddress module 21 | * [Automate SSH connections with netmiko](https://codingnetworker.com/2016/03/automate-ssh-connections-with-netmiko/) - automate SSH connection with netmiko and visualize the results using HTML, CSS and Javascript 22 | * [Parse Cisco IOS configurations with RegEx ](https://codingnetworker.com/2016/05/parse-cisco-ios-configurations-regex/) - some basic examples how to parse Cisco IOS configuration using regular expressions 23 | * [Parse Cisco IOS configuration using ciscoconfparse](https://codingnetworker.com/2016/06/parse-cisco-ios-configuration-ciscoconfparse/) - examples how to parse Cisco IOS configuration using ciscoconfparse (follow up to the last post) 24 | * [Extract CLI commands from Session Logs](https://codingnetworker.com/2016/08/extract-cli-commands-session-logs/) - short script to split multiple CLI commands and outputs from multiple text files (e.g. putty session logs) 25 | * [Pandas DataFrame 101](https://codingnetworker.com/2016/09/pandas-dataframes-101/) - introduction to pandas DataFrames 26 | * [Merge DataFrames in Pandas](https://codingnetworker.com/2016/11/merge-dataframes-pandas/) - how to merge pandas DataFrames based on an example using Excel 27 | * [Reliable Config Generation with Python](https://codingnetworker.com/2018/07/reliable-config-generation-with-python/) - wrapper around the Jinja2 environment that provides several filters for network configuration templates and a content error check functionality 28 | 29 | In the post [about Vagrant](https://codingnetworker.com/2015/09/use-vagrant-to-run-the-python-examples/), I'll explain how to execute the examples within a virtual machine. This Vagrant VM also contains a pre-configured Jupyter environment, which is described in more detail in the post about [Jupyter: an interactive web-based python shell](https://codingnetworker.com/2015/11/jupyter-interactive-web-based-python-shell/). -------------------------------------------------------------------------------- /Vagrantfile: -------------------------------------------------------------------------------- 1 | # -*- mode: ruby -*- 2 | # vi: set ft=ruby : 3 | 4 | # All Vagrant configuration is done below. The "2" in Vagrant.configure 5 | # configures the configuration version (we support older styles for 6 | # backwards compatibility). Please don't change it unless you know what 7 | # you're doing. 8 | Vagrant.configure(2) do |config| 9 | # The most common configuration options are documented and commented below. 10 | # For a complete reference, please see the online documentation at 11 | # https://docs.vagrantup.com. 12 | 13 | # Every Vagrant development environment requires a box. You can search for 14 | # boxes at https://atlas.hashicorp.com/search. 15 | config.vm.box = "ubuntu/trusty64" 16 | 17 | # Disable automatic box update checking. If you disable this, then 18 | # boxes will only be checked for updates when the user runs 19 | # `vagrant box outdated`. This is not recommended. 20 | # config.vm.box_check_update = false 21 | 22 | # Create a forwarded port mapping which allows access to a specific port 23 | # within the machine from a port on the host machine. In the example below, 24 | # accessing "localhost:8080" will access port 80 on the guest machine. 25 | config.vm.network "forwarded_port", guest: 8888, host: 8080 26 | 27 | # Create a private network, which allows host-only access to the machine 28 | # using a specific IP. 29 | # config.vm.network "private_network", ip: "192.168.33.10" 30 | 31 | # Create a public network, which generally matched to bridged network. 32 | # Bridged networks make the machine appear as another physical device on 33 | # your network. 34 | # config.vm.network "public_network" 35 | 36 | # Share an additional folder to the guest VM. The first argument is 37 | # the path on the host to the actual folder. The second argument is 38 | # the path on the guest to mount the folder. And the optional third 39 | # argument is a set of non-required options. 40 | # config.vm.synced_folder "../data", "/vagrant_data" 41 | 42 | # Provider-specific configuration so you can fine-tune various 43 | # backing providers for Vagrant. These expose provider-specific options. 44 | # Example for VirtualBox: 45 | # 46 | config.vm.provider "virtualbox" do |vb| 47 | # Customize the amount of memory on the VM: 48 | vb.memory = "1024" 49 | end 50 | # 51 | # View the documentation for the provider you are using for more 52 | # information on available options. 53 | 54 | # I use a little hack on this script to get the vagrant file also working on Windows 55 | config.vm.provision "shell", 56 | inline: "sudo apt-get install ansible -y && ansible-playbook -i 'localhost,' -c local /vagrant/setup.yaml", 57 | keep_color: true 58 | 59 | config.vm.post_up_message = "access the machine using SSH using port 2222 or with the URL 'http://localhost:8080'" 60 | end 61 | -------------------------------------------------------------------------------- /automate-ssh-with-netmiko/collect-cdp-information.py: -------------------------------------------------------------------------------- 1 | """ 2 | This script collects the CDP information from a Cisco device using SSH. These information are converted to a JSON data 3 | structure that is used within a HTML page to create a simple network diagram. 4 | 5 | It uses netmiko, TextFSM and vis.js. 6 | """ 7 | import json 8 | import os 9 | import webbrowser 10 | import sys 11 | import textfsm 12 | from netmiko import ConnectHandler 13 | 14 | 15 | def get_cdp_neighbor_details(ip, username, password, enable_secret): 16 | """ 17 | get the CDP neighbor detail from the device using SSH 18 | 19 | :param ip: IP address of the device 20 | :param username: username used for the authentication 21 | :param password: password used for the authentication 22 | :param enable_secret: enable secret 23 | :return: 24 | """ 25 | # establish a connection to the device 26 | ssh_connection = ConnectHandler( 27 | device_type='cisco_ios', 28 | ip=ip, 29 | username=username, 30 | password=password, 31 | secret=enable_secret 32 | ) 33 | 34 | # enter enable mode 35 | ssh_connection.enable() 36 | 37 | # prepend the command prompt to the result (used to identify the local device) 38 | result = ssh_connection.find_prompt() + "\n" 39 | 40 | # execute the show cdp neighbor detail command 41 | # we increase the delay_factor for this command, because it take some time if many devices are seen by CDP 42 | result += ssh_connection.send_command("show cdp neighbor detail", delay_factor=2) 43 | 44 | # close SSH connection 45 | ssh_connection.disconnect() 46 | 47 | return result 48 | 49 | 50 | if __name__ == "__main__": 51 | if len(sys.argv) != 5: 52 | print("\nplease provide the following arguments:") 53 | print("\tcollect-cdp-information.py \n\n") 54 | sys.exit(0) 55 | 56 | target_ip = sys.argv[1] 57 | username = sys.argv[2] 58 | password = sys.argv[3] 59 | secret = sys.argv[4] 60 | 61 | try: 62 | print("collect CDP information from device %s..." % target_ip) 63 | cdp_det_result = get_cdp_neighbor_details( 64 | ip=target_ip, 65 | username=username, 66 | password=password, 67 | enable_secret=secret 68 | ) 69 | 70 | found_hosts = [] 71 | nodes = [] 72 | edges = [] 73 | 74 | # parse the show cdp details command using TextFSM 75 | print("parse results...") 76 | re_table = textfsm.TextFSM(open("show_cdp_neighbor_detail.textfsm")) 77 | fsm_results = re_table.ParseText(cdp_det_result) 78 | local_hostname = "not discovered" 79 | 80 | counter = 1 81 | for e in fsm_results: 82 | if len(nodes) == 0: 83 | # add local node (always ID 1) 84 | node = { 85 | "id": counter, 86 | "label": e[0], 87 | "group": "root_device" 88 | } 89 | 90 | counter += 1 91 | nodes.append(node) 92 | 93 | # add new node 94 | remote_node = e[1] 95 | if remote_node not in found_hosts: 96 | # add new node 97 | node = { 98 | "id": counter, 99 | "label": remote_node, 100 | "title": "Mgmt-IP:
%s

Platform:
" 101 | "%s

Version:
%s" % (e[2], e[3], e[6]), 102 | "group": "attached_device" 103 | } 104 | nodes.append(node) 105 | 106 | # add new connection 107 | edge = { 108 | "from": 1, 109 | "to": counter, 110 | "title": "from: %s
to: %s" % (e[5], e[4]), 111 | "label": "", 112 | "value": 0, 113 | "font": { 114 | "align": "top" 115 | } 116 | } 117 | edges.append(edge) 118 | found_hosts.append(remote_node) 119 | counter += 1 120 | 121 | else: 122 | # only add connection of existing device 123 | current_node = None 124 | for n in nodes: 125 | if remote_node in n["label"]: 126 | current_node = n 127 | break 128 | 129 | if current_node: 130 | # search existing connection and increase the value of the link 131 | for edge in edges: 132 | if edge["to"] == current_node["id"]: 133 | edge["value"] += 10 134 | edge["title"] += "
from: %s
to: %s" % (e[4], e[5]) 135 | 136 | else: 137 | # not found 138 | print("host %s should exist, but was not found in the dictionary." % remote_node) 139 | 140 | data = { 141 | "nodes": nodes, 142 | "edges": edges 143 | } 144 | print("write results to data.js...") 145 | datajs = "var data = " + json.dumps(data, indent=4) 146 | if os.path.exists("data.js"): 147 | os.remove("data.js") 148 | 149 | f = open("data.js", "w") 150 | f.write(datajs) 151 | f.close() 152 | 153 | print("open browser with results....") 154 | webbrowser.open_new_tab(os.path.abspath("data.js")) 155 | print("done") 156 | 157 | except Exception as ex: 158 | print("Exception occurred: %s" % ex) 159 | -------------------------------------------------------------------------------- /automate-ssh-with-netmiko/result.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Network - show CDP neighbor details 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 21 | 22 | 23 | 89 | 90 | -------------------------------------------------------------------------------- /automate-ssh-with-netmiko/show_cdp_neighbor_detail.textfsm: -------------------------------------------------------------------------------- 1 | Value Filldown local_host (\S+) 2 | Value Required dest_host (\S+) 3 | Value mgmt_ip (.*) 4 | Value platform (.*) 5 | Value remote_port (.*) 6 | Value local_port (.*) 7 | Value version (.*) 8 | 9 | Start 10 | ^${local_host}[>#].* 11 | ^Device ID: ${dest_host} 12 | ^Entry address\(es\): -> ParseIP 13 | ^Platform: ${platform}, 14 | ^Interface: ${local_port}, Port ID \(outgoing port\): ${remote_port} 15 | ^Version : -> GetVersion 16 | 17 | ParseIP 18 | ^.*IP address: ${mgmt_ip} -> Start 19 | 20 | GetVersion 21 | ^${version} -> Record Start -------------------------------------------------------------------------------- /cisco-ios-config-parameter-extraction/ciscoconfiparse_example.py: -------------------------------------------------------------------------------- 1 | """ 2 | example script how to extract parameters from a Cisco IOS configuration using ciscoconfparse 3 | """ 4 | import json 5 | from ciscoconfparse import CiscoConfParse 6 | from ciscoconfparse.ccp_util import IPv4Obj 7 | 8 | if __name__ == "__main__": 9 | # the result dictionary 10 | result = { 11 | "features": [], 12 | "interfaces": {} 13 | } 14 | 15 | # create CiscoConfParse object using a configuration file stored in the 16 | # same directory as the script 17 | confparse = CiscoConfParse("example_config.txt") 18 | 19 | # check if OSPF is used as the routing protocol 20 | # the following regex_pattern matches only the "router ospf " command (no VRFs) 21 | ospf_regex_pattern = r"^router ospf \d+$" 22 | 23 | # in this case, we will simply check that the ospf router command is part of the config 24 | is_ospf_in_use = confparse.has_line_with(ospf_regex_pattern) 25 | 26 | if is_ospf_in_use: 27 | print("==> OSPF is used in this configuration") 28 | result["features"].append("ospf") 29 | else: 30 | print("==> OSPF is not used in this configuration") 31 | 32 | # extract the interface name and description 33 | # first, we get all interface commands from the configuration 34 | interface_cmds = confparse.find_objects(r"^interface ") 35 | 36 | # iterate over the resulting IOSCfgLine objects 37 | for interface_cmd in interface_cmds: 38 | # get the interface name (remove the interface command from the configuration line) 39 | intf_name = interface_cmd.text[len("interface "):] 40 | result["interfaces"][intf_name] = {} 41 | 42 | # search for the description command, if not set use "not set" as value 43 | result["interfaces"][intf_name]["description"] = "not set" 44 | for cmd in interface_cmd.re_search_children(r"^ description "): 45 | result["interfaces"][intf_name]["description"] = cmd.text.strip()[len("description "):] 46 | 47 | # extract IP addresses if defined 48 | IPv4_REGEX = r"ip\saddress\s(\S+\s+\S+)" 49 | for cmd in interface_cmd.re_search_children(IPv4_REGEX): 50 | # ciscoconfparse provides a helper function for this task 51 | ipv4_addr = interface_cmd.re_match_iter_typed(IPv4_REGEX, result_type=IPv4Obj) 52 | 53 | result["interfaces"][intf_name].update({ 54 | "ipv4": { 55 | "address": ipv4_addr.ip.exploded, 56 | "netmask": ipv4_addr.netmask.exploded 57 | } 58 | }) 59 | 60 | print("\nEXTRACTED PARAMETERS\n") 61 | print(json.dumps(result, indent=4)) -------------------------------------------------------------------------------- /cisco-ios-config-parameter-extraction/example_config.txt: -------------------------------------------------------------------------------- 1 | ! 2 | hostname my-example-switch 3 | ! 4 | ! 5 | vlan 200 6 | name Segment_A 7 | ! 8 | vlan 200 9 | name Segment_B 10 | ! 11 | interface Loopback0 12 | description Management IP address 13 | ip address 1.1.1.1 255.255.255.255 14 | ! 15 | interface Vlan1 16 | no ip address 17 | ! 18 | interface Vlan200 19 | ip address 10.1.1.1 255.255.255.0 20 | ip helper-address 10.254.0.100 21 | no ip redirects 22 | no ip unreachables 23 | no ip proxy-arp 24 | ! 25 | interface Vlan300 26 | description vlan 300 interface (no ospf neighbors) 27 | ip address 10.2.2.1 255.255.255.0 28 | no ip redirects 29 | no ip unreachables 30 | no ip proxy-arp 31 | ! 32 | interface FastEthernet1/1 33 | no switchport 34 | description routed port Fa1/1 35 | ip address 10.3.3.1 255.255.255.0 36 | ! 37 | router ospf 1 38 | router-id 1.1.1.1 39 | log-adjacency-changes detail 40 | auto-cost reference-bandwidth 100000 41 | passive-interface Vlan300 42 | network 10.1.1.1 0.0.0.0 area 0 43 | network 10.2.2.1 0.0.0.0 area 0 44 | default-information originate 45 | ! 46 | ! -------------------------------------------------------------------------------- /cisco-ios-config-parameter-extraction/regular_expression_example.py: -------------------------------------------------------------------------------- 1 | """ 2 | example script how to extract parameters from a Cisco IOS configuration using regular expressions 3 | """ 4 | import sys 5 | import re 6 | import json 7 | 8 | if __name__ == "__main__": 9 | config_file_name = "example_config.txt" 10 | 11 | # the result dictionary 12 | result = { 13 | "features": [], 14 | "interfaces": {} 15 | } 16 | 17 | # read the example configuration 18 | try: 19 | file = open(config_file_name) 20 | sample_config = file.read() 21 | file.close() 22 | 23 | except Exception as ex: 24 | print("Cannot read configuration (%s), terminate script") 25 | sys.exit(1) 26 | 27 | # check if OSPF is used as the routing protocol 28 | # the following regex_pattern matches only the "router ospf " command (no VRFs) 29 | ospf_regex_pattern = r"^router ospf \d+$" 30 | 31 | # we will use the re.search() function, because the re.match() function ignores the MULTILINE flag 32 | # if the command is not found, the return value is None 33 | is_ospf_in_use = True if re.search(ospf_regex_pattern, sample_config, re.MULTILINE) else False 34 | 35 | if is_ospf_in_use: 36 | print("==> OSPF is used in this configuration") 37 | result["features"].append("ospf") 38 | else: 39 | print("==> OSPF is not used in this configuration") 40 | 41 | # extract the interface name and description 42 | interface_descriptions = re.finditer(r"^(interface (?P\S+))\n" 43 | r"( .*\n)*" 44 | r"( description (?P.*))\n", 45 | sample_config, 46 | re.MULTILINE) 47 | 48 | for intf_part in interface_descriptions: 49 | print("==> found interface '%s' with description '%s'" % (intf_part.group("intf_name"), 50 | intf_part.group("description"))) 51 | result["interfaces"][intf_part.group("intf_name")] = { 52 | "description": intf_part.group("description") if intf_part.group("description") else "not set" 53 | } 54 | 55 | # extract the IPv4 address of the interfaces 56 | interface_ips = re.finditer(r"^(interface (?P.*)\n)" 57 | r"( .*\n)*" 58 | r"( ip address (?P\S+) (?P\S+))\n", 59 | sample_config, 60 | re.MULTILINE) 61 | 62 | for intf_ip in interface_ips: 63 | print("==> found interface '%s' with ip '%s/%s'" % (intf_ip.group("intf_name"), 64 | intf_ip.group("ipv4_address"), 65 | intf_ip.group("subnet_mask"))) 66 | # create interface name if not already exist 67 | if intf_ip.group("intf_name") not in result["interfaces"].keys(): 68 | result["interfaces"][intf_ip.group("intf_name")] = {} 69 | 70 | result["interfaces"][intf_ip.group("intf_name")].update({ 71 | "ipv4": { 72 | "address": intf_ip.group("ipv4_address"), 73 | "netmask": intf_ip.group("subnet_mask") 74 | } 75 | }) 76 | 77 | print("\nEXTRACTED PARAMETERS\n") 78 | print(json.dumps(result, indent=4)) 79 | -------------------------------------------------------------------------------- /cisco-nx-api-example/interface-description-cleaner.py: -------------------------------------------------------------------------------- 1 | """ 2 | interface description cleaner 3 | ----------------------------- 4 | 5 | This script will utilize the Cisco NX-API to update the interface descriptions on a Cisco Nexus switches 6 | based on CDP information. 7 | 8 | """ 9 | import json 10 | import requests 11 | import re 12 | 13 | # todo update the device information when testing the script 14 | # get CDP information from the following clients and update there configuration 15 | hosts = [ 16 | "10.1.1.1", 17 | "10.1.1.2" 18 | ] 19 | 20 | # credentials to use on the Cisco NX-API 21 | dev_username = "setup" 22 | dev_password = "setup" 23 | 24 | # HTTPs server port, which is used on every Switch to connect to the Cisco NX-API 25 | HTTPS_SERVER_PORT = "8181" 26 | 27 | """ 28 | ------------------------------------------------------------------------------------------------------------------------ 29 | some helper functions to work with the Cisco NX-API 30 | """ 31 | # suppress the unverified request messages (when using self-signed certificates) 32 | requests.packages.urllib3.disable_warnings() 33 | 34 | def nxapi_cli_conf(commands, hostname, username, password): 35 | """ 36 | executes configure commands on the given host using Cisco NX-API 37 | 38 | :param username: username for authentication 39 | :param password: password for authentication 40 | :param hostname: the hostname, where the Cisco NX-API call must be executed 41 | :param commands: the configuration commands that should be executed on the switch using Cisco NX-API 42 | """ 43 | # convert the given configuration commands to a format which can be used within the Cisco NX-API and verify 44 | # that the configuration script does not end with the termination sign (lead to an error in the last command) 45 | commands = commands.replace("\n"," ; ") 46 | if commands.endswith(" ; "): 47 | commands = commands[:-3] 48 | 49 | payload = { 50 | "ins_api": { 51 | "version": "1.2", 52 | "type": "cli_conf", 53 | "chunk": "0", # do not chunk results 54 | "sid": "1", 55 | "input": commands, 56 | "output_format": "json" 57 | } 58 | } 59 | return nxapi_call(hostname, payload, username, password, "json") 60 | 61 | def nxapi_cli_show(show_command, hostname, username, password): 62 | """ 63 | execute show command on the given host using Cisco NX-API 64 | 65 | :param username: username for authentication 66 | :param password: password for authentication 67 | :param hostname: the hostname, where the Cisco NX-API call must be executed 68 | :param show_command: the show command, that should be executed on the switch 69 | """ 70 | payload = [ 71 | { 72 | "jsonrpc": "2.0", 73 | "method": "cli", 74 | "params": { 75 | "cmd": show_command, 76 | "version": 1.2 77 | }, 78 | "id": 1 79 | } 80 | ] 81 | return nxapi_call(hostname, payload, username, password, "json-rpc") 82 | 83 | def nxapi_call(hostname, payload, username, password, content_type="json"): 84 | """ 85 | common NX-API call which includes some basic verification of the response 86 | 87 | :param hostname: the hostname, where the Cisco NX-API call must be executed 88 | :param payload: the payload for the NX-API call 89 | :param username: username for authentication 90 | :param password: password for authentication 91 | :param content_type: the content type of the payload, defaults to "JSON" 92 | """ 93 | headers={'content-type':'application/%s' % content_type} 94 | response = requests.post("https://%s:%s/ins" % (hostname, HTTPS_SERVER_PORT), 95 | auth=(username, password), 96 | headers=headers, 97 | data=json.dumps(payload), 98 | verify=False, # disable SSH certificate verification 99 | timeout=4) 100 | if response.status_code == 200: 101 | # verify result if a cli_conf operation was performed 102 | if "ins_api" in payload: 103 | if "type" in payload['ins_api'].keys(): 104 | if "cli_conf" in payload['ins_api']['type']: 105 | for result in response.json()['ins_api']['outputs']['output']: 106 | if result['code'] != "200": 107 | print("--> partial configuration failed, please verify your configuration!") 108 | break 109 | return response.json() 110 | else: 111 | msg = "call to %s failed, status code %d (%s)" % (target_host, 112 | response.status_code, 113 | response.content.decode("utf-8")) 114 | print(msg) 115 | raise Exception(msg) 116 | 117 | def interface_shortener(interface_name): 118 | """ 119 | makes Cisco interface names shorter (e.g. 'Ethernet' to 'Eth') 120 | 121 | :param interface_name: 122 | """ 123 | interface_short_string_map = [ 124 | { "from": "Ethernet", "to": "Eth"}, 125 | { "from": "GigabitEthernet", "to": "Gi"}, 126 | { "from": "FastEthernet", "to": "Fa"}, 127 | { "from": "TenGigabitEthernet", "to": "Te"}, 128 | ] 129 | result = interface_name 130 | for s in interface_short_string_map: 131 | if re.match(r"^%s" % s['from'], result) is not None: 132 | result = result.replace(s['from'], s['to']) 133 | break 134 | return result 135 | 136 | """ 137 | ------------------------------------------------------------------------------------------------------------------------ 138 | the interface description cleaner script 139 | """ 140 | 141 | if __name__ == "__main__": 142 | print("----------------------------------------") 143 | print("start the interface description cleaner ") 144 | print("----------------------------------------") 145 | host_results = dict() 146 | for target_host in hosts: 147 | print("request CDP information for switch: %s" % target_host) 148 | result = nxapi_cli_show("show cdp neighbor detail", target_host, dev_username, dev_password) 149 | 150 | # dump neighbors 151 | neighbor_statements = result['result']['body']['TABLE_cdp_neighbor_detail_info']['ROW_cdp_neighbor_detail_info'] 152 | 153 | host_neighbors = list() 154 | 155 | if type(neighbor_statements) is not list: 156 | # convert to a list if only a single entry in a dictionary is received from the device 157 | neighbor_statements = [neighbor_statements] 158 | 159 | for neighbor in neighbor_statements: 160 | # remove SN and/or DNS prefix from hostname 161 | remote_host = neighbor['device_id'].split("(")[0] 162 | if "." in remote_host: 163 | remote_host = remote_host.split(".")[0] 164 | 165 | # descriptions should not be that long... 166 | local_interface = interface_shortener(neighbor['intf_id']) 167 | remote_interface = interface_shortener(neighbor['port_id']) 168 | 169 | entry = { 170 | "local_interface": local_interface, 171 | "remote_host": remote_host, 172 | "remote_interface": remote_interface, 173 | "remote_mgmt_ip": neighbor['v4mgmtaddr'] 174 | } 175 | host_neighbors.append(entry) 176 | 177 | host_results[target_host] = host_neighbors 178 | 179 | # generate change script per device and push to it 180 | for host in host_results.keys(): 181 | # create change script 182 | change_script = "" 183 | for entry in host_results[host]: 184 | change_script += "interface %s\n description *** %s, %s (%s)\n" % (entry['local_interface'], 185 | entry['remote_interface'], 186 | entry['remote_host'], 187 | entry['remote_mgmt_ip']) 188 | # verify that the output is correct 189 | print("apply change script: %s" % host) 190 | response = nxapi_cli_conf(change_script, host, dev_username, dev_password) 191 | 192 | print("finished successful") 193 | -------------------------------------------------------------------------------- /config-generator-with-custom-filters/config-generator-with-custom-filter.py: -------------------------------------------------------------------------------- 1 | """ 2 | config generator with custom filter 3 | ----------------------------------- 4 | 5 | This script will generate an interface configuration for a Cisco IOS and Juniper JUNOS device based on a common set of 6 | parameters to demonstrate the use of custom filters with the Jinja2 template engine. 7 | 8 | """ 9 | import jinja2 10 | import json 11 | import os 12 | from ipaddress import IPv4Network 13 | from slugify import slugify 14 | 15 | parameter_file = "parameters.json" 16 | template_file = "ip-interface-config.jinja2" 17 | output_directory = "_output" 18 | 19 | 20 | def dotted_decimal(prefix_length): 21 | """ 22 | converts the given prefix to a IPv4 dotted decimal representation 23 | :param prefix_length: 24 | :return: 25 | """ 26 | try: 27 | ip = IPv4Network("0.0.0.0/" + str(prefix_length)) 28 | return ip.netmask 29 | except Exception: 30 | return "[INVALID VALUE(" + str(prefix_length) + ")]" 31 | 32 | 33 | def slugify_string(text): 34 | """ 35 | convert the given string to a slug 36 | :param text: 37 | :return: 38 | """ 39 | return slugify(text) 40 | 41 | 42 | if __name__ == "__main__": 43 | # create Jinja2 template environment with the link to the current directory 44 | env = jinja2.Environment(loader=jinja2.FileSystemLoader(searchpath="."), 45 | trim_blocks=True, 46 | lstrip_blocks=True) 47 | 48 | # register custom filters on the jinja2 environment 49 | env.filters["dotted_decimal"] = dotted_decimal 50 | env.filters["slugify_string"] = slugify_string 51 | 52 | # load template file 53 | template = env.get_template(template_file) 54 | 55 | # just make sure that the output directory exists 56 | if not os.path.exists(output_directory): 57 | os.mkdir(output_directory) 58 | 59 | print("Load common parameter set and define vendors...") 60 | vendors = ["Cisco_IOS", "Juniper"] 61 | interface_parameter_json = json.load(open(parameter_file)) 62 | 63 | # create the templates for all vendors 64 | print("Create templates for all vendors...") 65 | for vendor in vendors: 66 | parameter = { 67 | "vendor": vendor, 68 | "feature_string": ["Infrastructure ACLs"], 69 | } 70 | parameter.update(interface_parameter_json.copy()) 71 | result = template.render(parameter) 72 | f = open(os.path.join(output_directory, vendor + "-ip_interfaces.config"), "w") 73 | f.write(result) 74 | f.close() 75 | print("Configuration '%s' created for %s" % (vendor + "-ip_interfaces.config", vendor)) 76 | 77 | print("DONE") 78 | -------------------------------------------------------------------------------- /config-generator-with-custom-filters/ip-interface-config.jinja2: -------------------------------------------------------------------------------- 1 | ! 2 | {% for interface in interfaces %} 3 | {% set vlan_name = interface.name|slugify_string %} 4 | {% if vendor == "Cisco_IOS" %} 5 | {% set subnet_mask = interface.prefix_length|dotted_decimal %} 6 | vlan {{ interface.vlan_id }} 7 | name {{ vlan_name }} 8 | interface vlan{{ interface.vlan_id }} 9 | description *** IP interface for {{ interface.name }} (VLAN {{ interface.vlan_id }}) 10 | ip address {{ interface.ip_address }} {{ subnet_mask }} 11 | ! 12 | {% elif vendor == "Juniper" %} 13 | set vlans {{ vlan_name }} vlan-id {{ interface.vlan_id }} 14 | edit interfaces vlan unit {{ interface.vlan_id }} 15 | set family inet address {{ interface.ip_address }}/{{ interface.prefix_length }} 16 | set description "*** IP interface for {{ interface.name }} (VLAN {{ interface.vlan_id }})" 17 | top 18 | set vlans {{ vlan_name }} l3-interface vlan.{{ interface.vlan_id }} 19 | ! 20 | {% else %} 21 | ! WARNING: UNKNOWN VENDOR 22 | {% endif %} 23 | {% endfor %} 24 | ! -------------------------------------------------------------------------------- /config-generator-with-custom-filters/parameters.json: -------------------------------------------------------------------------------- 1 | { 2 | "interfaces": [ 3 | { 4 | "vlan_id": 10, 5 | "ip_address": "10.0.10.1", 6 | "prefix_length": 24, 7 | "name": "Data VLAN" 8 | }, 9 | { 10 | "vlan_id": 20, 11 | "ip_address": "10.0.20.1", 12 | "prefix_length": 24, 13 | "name": "Voice VLAN" 14 | }, 15 | { 16 | "vlan_id": 30, 17 | "ip_address": "10.0.30.1", 18 | "prefix_length": 24, 19 | "name": "Server VLAN" 20 | }, 21 | { 22 | "vlan_id": 40, 23 | "ip_address": "10.0.40.1", 24 | "prefix_length": 24, 25 | "name": "Management VLAN" 26 | }, 27 | { 28 | "vlan_id": 50, 29 | "ip_address": "10.0.50.1", 30 | "prefix_length": 24, 31 | "name": "transit VLAN to CE router" 32 | } 33 | ] 34 | } -------------------------------------------------------------------------------- /config-generator-with-python-and-jinja2/csv_based_config_generator.py: -------------------------------------------------------------------------------- 1 | import jinja2 2 | import os 3 | 4 | template_file = "switch.j2" 5 | csv_parameter_file = "parameters.csv" 6 | config_parameters = [] 7 | output_directory = "_output" 8 | 9 | # 1. read the contents from the CSV files 10 | print("Read CSV parameter file...") 11 | f = open(csv_parameter_file) 12 | csv_content = f.read() 13 | f.close() 14 | 15 | # 2. for Jinja2, we need to convert the given CSV file into the a python 16 | # dictionary to get the script a bit more reusable, I will not statically 17 | # limit the possible header values (and therefore the variables) 18 | print("Convert CSV file to dictionaries...") 19 | csv_lines = csv_content.splitlines() 20 | headers = csv_lines[0].split(";") 21 | for i in range(1, len(csv_lines)): 22 | values = csv_lines[i].split(";") 23 | parameter_dict = dict() 24 | for h in range(0, len(headers)): 25 | parameter_dict[headers[h]] = values[h] 26 | config_parameters.append(parameter_dict) 27 | 28 | # 3. next we need to create the central Jinja2 environment and we will load 29 | # the Jinja2 template file 30 | print("Create Jinja2 environment...") 31 | env = jinja2.Environment(loader=jinja2.FileSystemLoader(searchpath=".")) 32 | template = env.get_template(template_file) 33 | 34 | # we will make sure that the output directory exists 35 | if not os.path.exists(output_directory): 36 | os.mkdir(output_directory) 37 | 38 | # 4. now create the templates 39 | print("Create templates...") 40 | for parameter in config_parameters: 41 | result = template.render(parameter) 42 | f = open(os.path.join(output_directory, parameter['hostname'] + ".config"), "w") 43 | f.write(result) 44 | f.close() 45 | print("Configuration '%s' created..." % (parameter['hostname'] + ".config")) 46 | print("DONE") 47 | -------------------------------------------------------------------------------- /config-generator-with-python-and-jinja2/json_based_config_generator.py: -------------------------------------------------------------------------------- 1 | import jinja2 2 | import json 3 | import os 4 | 5 | template_file = "switch_with_vlans.j2" 6 | json_parameter_file = "parameters.json" 7 | output_directory = "_output" 8 | 9 | # read the contents from the JSON files 10 | print("Read JSON parameter file...") 11 | config_parameters = json.load(open(json_parameter_file)) 12 | 13 | # next we need to create the central Jinja2 environment and we will load 14 | # the Jinja2 template file (the two parameters ensure a clean output in the 15 | # configuration file) 16 | print("Create Jinja2 environment...") 17 | env = jinja2.Environment(loader=jinja2.FileSystemLoader(searchpath="."), 18 | trim_blocks=True, 19 | lstrip_blocks=True) 20 | template = env.get_template(template_file) 21 | 22 | # we will make sure that the output directory exists 23 | if not os.path.exists(output_directory): 24 | os.mkdir(output_directory) 25 | 26 | # now create the templates 27 | print("Create templates...") 28 | for parameter in config_parameters: 29 | result = template.render(parameter) 30 | f = open(os.path.join(output_directory, parameter['hostname'] + "_with_vlans.config"), "w") 31 | f.write(result) 32 | f.close() 33 | print("Configuration '%s' created..." % (parameter['hostname'] + "_with_vlans.config")) 34 | print("DONE") 35 | -------------------------------------------------------------------------------- /config-generator-with-python-and-jinja2/parameters.csv: -------------------------------------------------------------------------------- 1 | hostname;domain_name;management_ip 2 | switch-a;domain.local;10.0.0.1 3 | switch-b;domain.local;10.0.0.2 4 | switch-c;domain.local;10.0.0.3 5 | switch-d;domain.local;10.0.0.4 -------------------------------------------------------------------------------- /config-generator-with-python-and-jinja2/parameters.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "hostname": "switch-a", 4 | "domain_name": "domain.local", 5 | "management_ip": "10.0.0.1", 6 | "vlans": [ 7 | { 8 | "name": "Data", 9 | "id": 100 10 | }, 11 | { 12 | "name": "Voice", 13 | "id": 200 14 | }, 15 | { 16 | "name": "Server", 17 | "id": 300 18 | } 19 | ] 20 | }, 21 | { 22 | "hostname": "switch-b", 23 | "domain_name": "domain.local", 24 | "management_ip": "10.0.0.2", 25 | "vlans": [ 26 | { 27 | "name": "Data", 28 | "id": 100 29 | }, 30 | { 31 | "name": "Voice", 32 | "id": 200 33 | }, 34 | { 35 | "name": "Server", 36 | "id": 300 37 | } 38 | ] 39 | }, 40 | { 41 | "hostname": "switch-c", 42 | "domain_name": "domain.local", 43 | "management_ip": "10.0.0.3", 44 | "vlans": [ 45 | { 46 | "name": "Data", 47 | "id": 100 48 | }, 49 | { 50 | "name": "Voice", 51 | "id": 200 52 | }, 53 | { 54 | "name": "Server", 55 | "id": 300 56 | } 57 | ] 58 | }, 59 | { 60 | "hostname": "switch-d", 61 | "domain_name": "domain.local", 62 | "management_ip": "10.0.0.4", 63 | "vlans": [ 64 | { 65 | "name": "Data", 66 | "id": 100 67 | }, 68 | { 69 | "name": "Voice", 70 | "id": 200 71 | }, 72 | { 73 | "name": "Server", 74 | "id": 300 75 | } 76 | ] 77 | } 78 | ] -------------------------------------------------------------------------------- /config-generator-with-python-and-jinja2/switch.j2: -------------------------------------------------------------------------------- 1 | ! 2 | ! Just a simple Cisco IOS configuration 3 | ! 4 | hostname {{ hostname }} 5 | ! 6 | ip domain-name {{ domain_name }} 7 | ! 8 | interface Loopback0 9 | description Management Interface 10 | ip address {{ management_ip }} 255.255.255.255 11 | ! 12 | interface FastEthernet 0/0 13 | description external interface 14 | ip address dhcp 15 | ! -------------------------------------------------------------------------------- /config-generator-with-python-and-jinja2/switch_with_vlans.j2: -------------------------------------------------------------------------------- 1 | ! 2 | ! Just a simple Cisco IOS configuration 3 | ! 4 | hostname {{ hostname }} 5 | ! 6 | ip domain-name {{ domain_name }} 7 | ! 8 | interface Loopback0 9 | description Management Interface 10 | ip address {{ management_ip }} 255.255.255.255 11 | ! 12 | interface FastEthernet 0/0 13 | description external interface 14 | ip address dhcp 15 | ! 16 | {% for vlan in vlans %} 17 | vlan {{ vlan.id }} 18 | name {{ vlan.name }} 19 | {% endfor %} 20 | ! -------------------------------------------------------------------------------- /create-hsrp-interface-configuration/cisco_ios_vlans.txt: -------------------------------------------------------------------------------- 1 | 2 | vlan 100 3 | name Data_100 4 | ! 5 | vlan 201 6 | name Data_201 7 | ! 8 | vlan 203 9 | name Data_203 10 | ! 11 | vlan 204 12 | name Data_204 13 | ! 14 | vlan 300 15 | name Server_300 16 | ! 17 | vlan 301 18 | name Server_301 19 | ! 20 | vlan 310 21 | name Server_310 22 | ! 23 | vlan 320 24 | name Server_320 25 | ! 26 | vlan 351 27 | name Server_351 28 | ! 29 | vlan 352 30 | name Server_352 31 | ! 32 | interface Vlan100 33 | ip address 10.0.100.1 255.255.255.0 34 | ip address 10.1.100.1 255.255.255.0 secondary 35 | ip address 10.2.100.1 255.255.255.0 secondary 36 | ip address 10.3.100.1 255.255.255.0 secondary 37 | ip address 10.4.100.1 255.255.255.0 secondary 38 | ! 39 | interface Vlan201 40 | ip address 10.0.200.254 255.255.255.0 41 | ip address 10.1.200.254 255.255.255.0 secondary 42 | ip address 10.2.200.254 255.255.255.0 secondary 43 | ip address 10.3.200.254 255.255.255.0 secondary 44 | ip address 10.4.200.254 255.255.255.0 secondary 45 | ! 46 | interface Vlan203 47 | ip address 10.0.203.1 255.255.255.0 48 | ! 49 | interface Vlan204 50 | ip address 10.0.204.1 255.255.255.0 51 | ! 52 | interface Vlan300 53 | ip address 10.10.0.128 255.255.255.0 54 | ! 55 | interface Vlan301 56 | ip address 10.10.1.1 255.255.255.0 57 | ! 58 | interface Vlan310 59 | ip address 10.10.10.1 255.255.255.0 60 | ! 61 | interface Vlan320 62 | ip address 10.10.20.126 255.255.255.128 63 | ! 64 | interface Vlan351 65 | ip address 10.10.51.126 255.255.255.128 66 | ! 67 | interface Vlan352 68 | ip address 10.10.52.126 255.255.255.128 69 | ! 70 | -------------------------------------------------------------------------------- /create-hsrp-interface-configuration/create-hsrp-interface-configuration.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | from ipaddress import IPv4Network, IPv4Interface, IPv4Address 4 | from ciscoconfparse import CiscoConfParse 5 | from ciscoconfparse.ccp_util import IPv4Obj 6 | 7 | 8 | output_directory = "_output" 9 | input_configuration_file = "cisco_ios_vlans.txt" 10 | primary_configuration_file = "primary_config.txt" 11 | secondary_configuration_file = "secondary_config.txt" 12 | 13 | 14 | if __name__ == "__main__": 15 | # just make sure that the output directory exists 16 | if not os.path.exists(output_directory): 17 | os.mkdir(output_directory) 18 | 19 | # load the input Cisco IOS configuration file 20 | print("load Cisco IOS configuration file...") 21 | try: 22 | parsed_config = CiscoConfParse(input_configuration_file) 23 | 24 | except Exception as ex: 25 | print(ex) 26 | sys.exit() 27 | 28 | # create objects for diff 29 | print("create primary and secondary configuration object...") 30 | primary_config = CiscoConfParse([ 31 | "!", 32 | "! primary switch interface configuration", 33 | "!", 34 | ]) 35 | secondary_config = CiscoConfParse([ 36 | "!", 37 | "! secondary switch interface configuration", 38 | "!", 39 | ]) 40 | 41 | # read all VLAN SVI's which has an ip address 42 | vlan_interfaces = parsed_config.find_objects_w_child("^interface Vlan", r"^ ip address.*") 43 | 44 | for vlan_interface in vlan_interfaces: 45 | # determine current ip address 46 | ipv4_addr = vlan_interface.re_match_iter_typed(r"ip\saddress\s(\S+\s+\S+)", result_type=IPv4Obj) 47 | 48 | # determine the current interface name and VLAN ID 49 | vlan_interface_string = vlan_interface.text.lstrip("interface ") 50 | vlan_id = vlan_interface_string.lstrip("Vlan") 51 | 52 | # the current SVI address is used as a HSRP virtual IP 53 | virtual_ip = ipv4_addr.ip_object 54 | 55 | # at this point we need to determine the next addresses which are used for the primary and secondary switch 56 | # we will try, if the next two addresses are part of the network, otherwise we will use the previous two 57 | # addresses 58 | ipv4_network = IPv4Network(ipv4_addr.network) 59 | if (ipv4_addr.ip_object + 1) in ipv4_network.hosts(): 60 | primary_ip = ipv4_addr.ip_object + 1 61 | secondary_ip = ipv4_addr.ip_object + 2 62 | else: 63 | primary_ip = ipv4_addr.ip_object - 1 64 | secondary_ip = ipv4_addr.ip_object - 2 65 | 66 | # check for secondary IPv4 addresses 67 | add_ip_addresses = [] 68 | secondary_ipv4_address_lines = vlan_interface.re_search_children(r"^ ip address .* secondary$") 69 | for sec_ipv4_cmd in secondary_ipv4_address_lines: 70 | # another way to convert the ip address command 71 | sec_ipv4_addr = sec_ipv4_cmd.text[len(" ip address "):] 72 | sec_ipv4_addr = sec_ipv4_addr.rstrip(" secondary") 73 | sec_ipv4_addr = sec_ipv4_addr.replace(" ", "/") 74 | 75 | # convert it to an IPv4Interface object from the ipaddresss module 76 | ip_address = IPv4Interface(sec_ipv4_addr) 77 | 78 | # store it for later processing 79 | add_ip_addresses.append(ip_address) 80 | 81 | # now add the configuration to the change scripts 82 | primary_config.append_line("interface %s" % vlan_interface_string) 83 | primary_config.append_line(" description *** VLAN SVI %s" % vlan_id) 84 | primary_config.append_line(" ip address %s %s" % (primary_ip, ipv4_addr.netmask)) 85 | for ipv4_address in add_ip_addresses: 86 | # determine primary IP address 87 | if IPv4Address(ipv4_address.ip + 1) in ipv4_address.network.hosts(): 88 | primary_ip = ipv4_address + 1 89 | else: 90 | primary_ip = ipv4_address - 1 91 | primary_config.append_line(" ip address %s %s secondary" % (primary_ip.ip, ipv4_address.netmask)) 92 | 93 | primary_config.append_line(" standby version 2") 94 | primary_config.append_line(" standby 1 ip %s" % virtual_ip) 95 | for ipv4_address in add_ip_addresses: 96 | primary_config.append_line(" standby 1 ip %s secondary" % ipv4_address.ip) 97 | 98 | primary_config.append_line(" standby 1 priority 255") 99 | primary_config.append_line(" standby 1 authentication md5 key-string vl%s" % vlan_id) 100 | primary_config.append_line("!") 101 | 102 | secondary_config.append_line("interface %s" % vlan_interface_string) 103 | secondary_config.append_line(" description *** VLAN SVI %s" % vlan_id) 104 | secondary_config.append_line(" ip address %s %s" % (secondary_ip, ipv4_addr.netmask)) 105 | for ipv4_address in add_ip_addresses: 106 | # determine secondary IP address 107 | if IPv4Address(ipv4_address.ip + 2) in ipv4_address.network.hosts(): 108 | secondary_ip = ipv4_address + 2 109 | else: 110 | secondary_ip = ipv4_address - 2 111 | secondary_config.append_line(" ip address %s %s secondary" % (secondary_ip.ip, ipv4_address.netmask)) 112 | 113 | secondary_config.append_line(" standby version 2") 114 | secondary_config.append_line(" standby 1 ip %s" % virtual_ip) 115 | for ipv4_address in add_ip_addresses: 116 | secondary_config.append_line(" standby 1 ip %s secondary" % ipv4_address.ip) 117 | 118 | secondary_config.append_line(" standby 1 priority 254") 119 | secondary_config.append_line(" standby 1 authentication md5 key-string vl%s" % vlan_id) 120 | secondary_config.append_line("!") 121 | 122 | # write results 123 | print("Write results...") 124 | primary_config.save_as(os.path.join(output_directory, primary_configuration_file)) 125 | secondary_config.save_as(os.path.join(output_directory, secondary_configuration_file)) 126 | -------------------------------------------------------------------------------- /json-code-example/load_json_data_example.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | # you can also use the open function to read the content of a file to a string 4 | json_data = """ { 5 | "key 1": "value 1", 6 | "key 2": "value 2", 7 | "decimal": 10, 8 | "boolean": true, 9 | "list": [1, 2, 3], 10 | "dictionary": { 11 | "child key 1": "child value", 12 | "child key 1": "child value" 13 | } 14 | }""" 15 | 16 | my_dict = json.loads(json_data) 17 | 18 | # keys() example 19 | print("keys at the first level within the dictionary") 20 | for key in my_dict.keys(): 21 | print(" %s" % key) 22 | print("\n\n") 23 | 24 | # access values in the dictionary 25 | print("string value: %s" % my_dict["key 1"]) 26 | print("decimal value: %d" % my_dict["decimal"]) 27 | print("decimal value: %r" % my_dict["boolean"]) 28 | print("list values: %s" % my_dict["list"]) 29 | -------------------------------------------------------------------------------- /json-code-example/validate_json_data_example.py: -------------------------------------------------------------------------------- 1 | """ 2 | example script to validate JSON data against a user defined schema 3 | """ 4 | import ipaddress 5 | import yaml 6 | import json 7 | from cerberus import Validator 8 | 9 | # JSON schema for the validation (as a python dictionary) 10 | schema_json = { 11 | "networks": { 12 | "type": "list", 13 | "schema": { 14 | "type": "dict", 15 | "schema": { 16 | "vlan": { 17 | "type": "dict", 18 | "schema": { 19 | "id": { 20 | "type": "integer", 21 | "min": 1, 22 | "max": 4094 23 | }, 24 | "name": { 25 | "type": "string" 26 | } 27 | } 28 | }, 29 | "ipv4": { 30 | "type": "dict", 31 | "schema": { 32 | "address": { 33 | "type": "ipv4address" 34 | }, 35 | "prefix_length": { 36 | "type": "integer" 37 | } 38 | } 39 | } 40 | } 41 | } 42 | } 43 | } 44 | 45 | # same schema as above (YAML formatted) 46 | raw_schema_yaml = """ 47 | networks: 48 | type: list 49 | schema: 50 | type: dict 51 | schema: 52 | vlan: 53 | type: dict 54 | schema: 55 | id: 56 | type: integer 57 | min: 1 58 | max: 4094 59 | name: 60 | type: string 61 | ipv4: 62 | type: dict 63 | schema: 64 | address: 65 | type: ipv4address 66 | prefix_length: 67 | type: integer 68 | """ 69 | schema_yaml = yaml.load(raw_schema_yaml) 70 | 71 | 72 | class NetworkDataJsonValidator(Validator): 73 | """ 74 | A simple JSON data validator with a custom data type for IPv4 addresses 75 | """ 76 | def _validate_type_ipv4address(self, field, value): 77 | """ 78 | checks that the given value is a valid IPv4 address 79 | """ 80 | try: 81 | # try to create an IPv4 address object using the python3 ipaddress module 82 | ipaddress.IPv4Address(value) 83 | except: 84 | self._error(field, "Not a valid IPv4 address") 85 | 86 | 87 | if __name__ == "__main__": 88 | # some example data 89 | valid_sample_data = { 90 | "networks": [ 91 | { 92 | "vlan" : { 93 | "id": 1, 94 | "name": "data" 95 | }, 96 | "ipv4": { 97 | "address": "10.1.1.1", 98 | "prefix_length": 24 99 | } 100 | } 101 | ] 102 | } 103 | invalid_sample_data = { 104 | "networks": [ 105 | { 106 | "vlan": { 107 | "id": 5000, 108 | "name": "data" 109 | }, 110 | "ipv4": { 111 | "address": "FE80::1", 112 | "prefix_length": 24 113 | } 114 | } 115 | ] 116 | } 117 | 118 | # create an instance of the NetworkDataJsonValidator 119 | validator_json = NetworkDataJsonValidator(schema_json) 120 | validator_yaml = NetworkDataJsonValidator(schema_yaml) 121 | 122 | # validate the valid sample data 123 | print("validate the valid sample data") 124 | result_json = validator_json.validate(valid_sample_data) 125 | print("--> data validation result (using the JSON expressed schema): %s" % result_json) 126 | result_yaml = validator_yaml.validate(valid_sample_data) 127 | print("--> data validation result (using the JSON expressed schema): %s" % result_yaml) 128 | 129 | # validate the invalid sample data 130 | print("validate the invalid sample data") 131 | result_json = validator_json.validate(invalid_sample_data) 132 | print("--> data validation result (using the JSON expressed schema): %s" % result_json) 133 | print("--> Validation errors:") 134 | print(json.dumps(validator_json.errors, indent=4)) 135 | 136 | result_yaml = validator_yaml.validate(invalid_sample_data) 137 | print("--> data validation result (using the JSON expressed schema): %s" % result_yaml) 138 | print("--> Validation errors:") 139 | print(json.dumps(validator_yaml.errors, indent=4)) 140 | -------------------------------------------------------------------------------- /json-code-example/view_json_data_example.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | my_dictionary = { 4 | "key 1": "Value 1", 5 | "key 2": "Value 2", 6 | "decimal": 100, 7 | "boolean": False, 8 | "list": [1, 2, 3], 9 | "dict": { 10 | "child key 1": "value 1", 11 | "child key 2": "value 2" 12 | } 13 | } 14 | 15 | print(json.dumps(my_dictionary)) 16 | print(json.dumps(my_dictionary, indent=4)) 17 | print(json.dumps(my_dictionary, indent=4, sort_keys=True)) 18 | -------------------------------------------------------------------------------- /jupyter-config/10-nb-import-hook.py: -------------------------------------------------------------------------------- 1 | """ 2 | from http://stackoverflow.com/questions/20186344/ipynb-import-another-ipynb-file 3 | """ 4 | import io, os, sys, types 5 | from IPython import get_ipython 6 | from nbformat import current 7 | from IPython.core.interactiveshell import InteractiveShell 8 | 9 | 10 | def find_notebook(fullname, path=None): 11 | """find a notebook, given its fully qualified name and an optional path 12 | 13 | This turns "foo.bar" into "foo/bar.ipynb" 14 | and tries turning "Foo_Bar" into "Foo Bar" if Foo_Bar 15 | does not exist. 16 | """ 17 | name = fullname.rsplit('.', 1)[-1] 18 | if not path: 19 | path = [''] 20 | for d in path: 21 | nb_path = os.path.join(d, name + ".ipynb") 22 | if os.path.isfile(nb_path): 23 | return nb_path 24 | # let import Notebook_Name find "Notebook Name.ipynb" 25 | nb_path = nb_path.replace("_", " ") 26 | if os.path.isfile(nb_path): 27 | return nb_path 28 | 29 | 30 | class NotebookLoader(object): 31 | """Module Loader for Jupyter Notebooks""" 32 | def __init__(self, path=None): 33 | self.shell = InteractiveShell.instance() 34 | self.path = path 35 | 36 | def load_module(self, fullname): 37 | """import a notebook as a module""" 38 | path = find_notebook(fullname, self.path) 39 | 40 | print ("importing Jupyter notebook from %s" % path) 41 | 42 | # load the notebook object 43 | with io.open(path, 'r', encoding='utf-8') as f: 44 | nb = current.read(f, 'json') 45 | 46 | 47 | # create the module and add it to sys.modules 48 | # if name in sys.modules: 49 | # return sys.modules[name] 50 | mod = types.ModuleType(fullname) 51 | mod.__file__ = path 52 | mod.__loader__ = self 53 | mod.__dict__['get_ipython'] = get_ipython 54 | sys.modules[fullname] = mod 55 | 56 | # extra work to ensure that magics that would affect the user_ns 57 | # actually affect the notebook module's ns 58 | save_user_ns = self.shell.user_ns 59 | self.shell.user_ns = mod.__dict__ 60 | 61 | try: 62 | for cell in nb.worksheets[0].cells: 63 | if cell.cell_type == 'code' and cell.language == 'python': 64 | # transform the input to executable Python 65 | code = self.shell.input_transformer_manager.transform_cell(cell.input) 66 | # run the code in themodule 67 | exec(code, mod.__dict__) 68 | finally: 69 | self.shell.user_ns = save_user_ns 70 | return mod 71 | 72 | 73 | class NotebookFinder(object): 74 | """Module finder that locates Jupyter Notebooks""" 75 | def __init__(self): 76 | self.loaders = {} 77 | 78 | def find_module(self, fullname, path=None): 79 | nb_path = find_notebook(fullname, path) 80 | if not nb_path: 81 | return 82 | 83 | key = path 84 | if path: 85 | # lists aren't hashable 86 | key = os.path.sep.join(path) 87 | 88 | if key not in self.loaders: 89 | self.loaders[key] = NotebookLoader(path) 90 | return self.loaders[key] 91 | 92 | sys.meta_path.append(NotebookFinder()) 93 | -------------------------------------------------------------------------------- /jupyter-config/about.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# about\n", 8 | "\n", 9 | "This VM provides a Jupyter/IPython environment bundled with **python 3** and the following modules/libraries:\n", 10 | "\n", 11 | "* Jinja2\n", 12 | "* ciscoconfparse\n", 13 | "* textfsm\n", 14 | "* requests\n", 15 | "\n", 16 | "These modules were part of previous post on my blog at the [Coding Networker](https://codingnetworker.com).\n" 17 | ] 18 | }, 19 | { 20 | "cell_type": "markdown", 21 | "metadata": {}, 22 | "source": [ 23 | "## jupyter home directory\n", 24 | "\n", 25 | "The following directories are availalable on the home directory:\n", 26 | "\n", 27 | "* **examples** - link to the `/vagrant` directory, that incudes all previous python examples\n", 28 | "* **notebooks** - link to the `/vagrant/notebooks` directory, that contains all example notebooks\n", 29 | "* **github** - repositories, that are cloned from GitHub during the provisioning of the machine" 30 | ] 31 | }, 32 | { 33 | "cell_type": "markdown", 34 | "metadata": {}, 35 | "source": [ 36 | "## import other notebooks\n", 37 | "\n", 38 | "This environment is preconfigured to import other notebooks, which are located in a directory with a `__init__.py` file.\n", 39 | "\n", 40 | "The following code shows an example of this function:" 41 | ] 42 | }, 43 | { 44 | "cell_type": "code", 45 | "execution_count": 1, 46 | "metadata": { 47 | "collapsed": false 48 | }, 49 | "outputs": [ 50 | { 51 | "name": "stdout", 52 | "output_type": "stream", 53 | "text": [ 54 | "importing Jupyter notebook from /home/vagrant/notebooks/import_test.ipynb\n" 55 | ] 56 | } 57 | ], 58 | "source": [ 59 | "from notebooks.import_test import echo_function" 60 | ] 61 | }, 62 | { 63 | "cell_type": "code", 64 | "execution_count": 2, 65 | "metadata": { 66 | "collapsed": false 67 | }, 68 | "outputs": [ 69 | { 70 | "name": "stdout", 71 | "output_type": "stream", 72 | "text": [ 73 | "Hello\n" 74 | ] 75 | }, 76 | { 77 | "data": { 78 | "text/plain": [ 79 | "'Hello'" 80 | ] 81 | }, 82 | "execution_count": 2, 83 | "metadata": {}, 84 | "output_type": "execute_result" 85 | } 86 | ], 87 | "source": [ 88 | "echo_function(\"Hello\")" 89 | ] 90 | } 91 | ], 92 | "metadata": { 93 | "kernelspec": { 94 | "display_name": "Python 3", 95 | "language": "python", 96 | "name": "python3" 97 | }, 98 | "language_info": { 99 | "codemirror_mode": { 100 | "name": "ipython", 101 | "version": 3 102 | }, 103 | "file_extension": ".py", 104 | "mimetype": "text/x-python", 105 | "name": "python", 106 | "nbconvert_exporter": "python", 107 | "pygments_lexer": "ipython3", 108 | "version": "3.4.3" 109 | } 110 | }, 111 | "nbformat": 4, 112 | "nbformat_minor": 0 113 | } 114 | -------------------------------------------------------------------------------- /jupyter-config/jupyter-notebook.upstart: -------------------------------------------------------------------------------- 1 | description "Jupyter Upstart script" 2 | 3 | start on filesystem or runlevel [2345] 4 | stop on shutdown 5 | 6 | script 7 | export HOME="/home/vagrant/" cd $HOME 8 | echo $$ > /var/run/jupyter_start.pid 9 | exec jupyter-notebook --config='/home/vagrant/.jupyter/jupyter_notebook.config.py' 10 | end script 11 | 12 | pre-start script 13 | echo "[`date`] Starting Jupyter Notebook Server" >> /var/log/jupyter-ntb.log 14 | end script 15 | 16 | pre-stop script 17 | rm /var/run/ipython_start.pid 18 | echo "[`date`] Stopping Jupyter Notebook" >> /var/jupyter/Jupyter-ntb.log 19 | end script -------------------------------------------------------------------------------- /jupyter-config/jupyter_notebook_config.py: -------------------------------------------------------------------------------- 1 | 2 | # configuration file for jupyter 3 | c.NotebookApp.notebook_dir = u"/home/vagrant/" 4 | c.NotebookApp.ip = "*" 5 | c.NotebookApp.open_browser = False -------------------------------------------------------------------------------- /migrate-static-arp-entries/cisco_static_arp_configuration.txt: -------------------------------------------------------------------------------- 1 | ! 2 | interface Vlan100 3 | ip address 10.0.100.1 255.255.255.0 4 | ! 5 | interface Vlan101 6 | ip address 10.0.101.254 255.255.255.0 7 | ! 8 | interface Vlan102 9 | ip address 10.0.102.1 255.255.255.128 10 | ! 11 | interface Vlan103 12 | ip address 10.0.103.1 255.255.255.128 13 | ! 14 | arp 10.0.100.80 0100.5e7f.9271 ARPA 15 | arp 10.0.100.105 0100.5e7f.8a64 ARPA 16 | arp 10.0.100.109 0100.5e7f.8ef0 ARPA 17 | arp 10.0.100.12 0100.5e7f.8964 ARPA 18 | arp 10.0.101.124 0100.5e7f.907b ARPA 19 | arp 10.0.101.122 0100.5e7f.9b72 ARPA 20 | arp 10.0.102.168 0100.5e7f.835a ARPA 21 | arp 10.0.102.116 0100.5e7f.9b79 ARPA 22 | arp 10.0.102.110 0100.5e7f.8e65 ARPA 23 | arp 10.0.103.105 0100.5e7f.8162 ARPA 24 | arp 10.0.103.203 0100.5e7f.826b ARPA 25 | arp 10.0.103.123 0100.5e7f.987e ARPA 26 | arp 10.0.103.123 0100.5e7f.9a7c ARPA 27 | ! -------------------------------------------------------------------------------- /migrate-static-arp-entries/create-migration-templates.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import os 3 | from ipaddress import IPv4Address, IPv4Network 4 | from ciscoconfparse import CiscoConfParse 5 | from ciscoconfparse.ccp_util import IPv4Obj 6 | 7 | # existing configuration file and output dir 8 | existing_configuration = "cisco_static_arp_configuration.txt" 9 | output_dir = "_output" 10 | 11 | 12 | def get_vlan_svi_records_from_existing_configuration(cisco_conf_parse_obj): 13 | """ 14 | parse VLAN SVI interfaces with IP address 15 | :param cisco_conf_parse_obj: Instance of CiscoConfParse 16 | :return: list with dictionaries that contain the VLAN ID, IP address and subnet mask 17 | """ 18 | # read all VLAN SVI's which has an ip address 19 | vlan_interfaces = cisco_conf_parse_obj.find_objects_w_child("^interface Vlan", r'^ ip address.*') 20 | vlan_svi_records = list() 21 | 22 | for vlan_interface in vlan_interfaces: 23 | vlan_svi_record = dict() 24 | 25 | # determine current ip address 26 | ipv4_addr = vlan_interface.re_match_iter_typed(r"ip\saddress\s(\S+\s+\S+)", result_type=IPv4Obj) 27 | 28 | # determine the current interface name and VLAN ID 29 | vlan_interface_string = vlan_interface.text.lstrip("interface ") 30 | vlan_id = vlan_interface_string.lstrip("Vlan") 31 | 32 | vlan_svi_record['ipv4_addr'] = str(ipv4_addr.ip) 33 | vlan_svi_record['ipv4_netmask'] = str(ipv4_addr.netmask) 34 | vlan_svi_record['vlan_id'] = vlan_id 35 | vlan_svi_records.append(vlan_svi_record) 36 | 37 | return vlan_svi_records 38 | 39 | 40 | if __name__ == "__main__": 41 | # verify that output directory exists 42 | if not os.path.exists(output_dir): 43 | os.mkdir(output_dir) 44 | 45 | print("Load confguration file...") 46 | try: 47 | parsed_config = CiscoConfParse(existing_configuration) 48 | 49 | except Exception as ex: 50 | print("FAILED") 51 | print(">>> loading of configuration failed, script execution terminated") 52 | print(ex) 53 | sys.exit() 54 | 55 | print("Load VLAN SVIs from configuration...") 56 | vlan_svis = get_vlan_svi_records_from_existing_configuration(parsed_config) 57 | 58 | print("Load static ARP entries from configuration...") 59 | # get the static ARP entries 60 | static_arp_entries = parsed_config.find_objects("^arp\s(\S+\s+\S+)") 61 | 62 | for static_arp_entry in static_arp_entries: 63 | arp_record = dict() 64 | 65 | # split the arp command and get the required infos 66 | # result looks like: ['arp', '10.0.100.115', '0100.5e7f.9271', 'ARPA'] 67 | arr_obj = static_arp_entry.text.split() 68 | ipv4 = arr_obj[1] 69 | mac = arr_obj[2] 70 | 71 | # now we create an IP address object from the ARP entry 72 | arp_ipv4_addr = IPv4Address(ipv4) 73 | 74 | # assign static arp entry to the VLAN SVI interface 75 | for vlan_svi in vlan_svis: 76 | svi_ipv4_network = IPv4Network(vlan_svi['ipv4_addr'] + "/" + vlan_svi['ipv4_netmask'], strict=False) 77 | if arp_ipv4_addr in svi_ipv4_network.hosts(): 78 | # extend the model if the correct IP network is found 79 | if "static_arps" not in vlan_svi.keys(): 80 | vlan_svi['static_arps'] = list() 81 | record = { 82 | 'ipv4_host': ipv4, 83 | 'mac': mac 84 | } 85 | vlan_svi['static_arps'].append(record) 86 | 87 | # a static ARP is only defined on a single interface 88 | break 89 | 90 | print("Write results to file...") 91 | cisco_nxos_template = CiscoConfParse(['!']) 92 | 93 | for vlan_svi in vlan_svis: 94 | cisco_nxos_template.append_line("interface Vlan%s" % vlan_svi['vlan_id']) 95 | for static_arp in vlan_svi['static_arps']: 96 | cisco_nxos_template.append_line(" ip arp %s %s" % (static_arp['ipv4_host'], static_arp['mac'])) 97 | cisco_nxos_template.append_line('!') 98 | 99 | cisco_nxos_template.save_as(os.path.join(output_dir, "cisco_nxos_config.txt")) 100 | -------------------------------------------------------------------------------- /networkconfgen/networkconfgen_example.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# `networkconfgen` examples\n", 8 | "\n" 9 | ] 10 | }, 11 | { 12 | "cell_type": "code", 13 | "execution_count": 1, 14 | "metadata": {}, 15 | "outputs": [], 16 | "source": [ 17 | "from networkconfgen import NetworkConfGen\n", 18 | "\n", 19 | "confgen = NetworkConfGen()" 20 | ] 21 | }, 22 | { 23 | "cell_type": "markdown", 24 | "metadata": {}, 25 | "source": [ 26 | "## Basic usage\n", 27 | "\n", 28 | "Basic usage with `str` objects:" 29 | ] 30 | }, 31 | { 32 | "cell_type": "code", 33 | "execution_count": 2, 34 | "metadata": {}, 35 | "outputs": [ 36 | { 37 | "data": { 38 | "text/plain": [ 39 | "networkconfgen.base.NetworkConfGenResult" 40 | ] 41 | }, 42 | "execution_count": 2, 43 | "metadata": {}, 44 | "output_type": "execute_result" 45 | } 46 | ], 47 | "source": [ 48 | "template = \"\"\"\n", 49 | "!\n", 50 | "hostname {{ hostname }}\n", 51 | "!\n", 52 | "\"\"\"\n", 53 | "\n", 54 | "parameters = {\n", 55 | " \"hostname\": \"demo\"\n", 56 | "}\n", 57 | "\n", 58 | "result = confgen.render_from_string(template_content=template, parameters=parameters)\n", 59 | "type(result)" 60 | ] 61 | }, 62 | { 63 | "cell_type": "markdown", 64 | "metadata": {}, 65 | "source": [ 66 | "Verify that the parsing process was successful" 67 | ] 68 | }, 69 | { 70 | "cell_type": "code", 71 | "execution_count": 3, 72 | "metadata": {}, 73 | "outputs": [ 74 | { 75 | "data": { 76 | "text/plain": [ 77 | "False" 78 | ] 79 | }, 80 | "execution_count": 3, 81 | "metadata": {}, 82 | "output_type": "execute_result" 83 | } 84 | ], 85 | "source": [ 86 | "result.render_error" 87 | ] 88 | }, 89 | { 90 | "cell_type": "markdown", 91 | "metadata": {}, 92 | "source": [ 93 | "Display the results (raw and cleaned)" 94 | ] 95 | }, 96 | { 97 | "cell_type": "code", 98 | "execution_count": 4, 99 | "metadata": {}, 100 | "outputs": [ 101 | { 102 | "name": "stdout", 103 | "output_type": "stream", 104 | "text": [ 105 | "\n", 106 | "!\n", 107 | "hostname demo\n", 108 | "!\n" 109 | ] 110 | } 111 | ], 112 | "source": [ 113 | "print(result.template_result)" 114 | ] 115 | }, 116 | { 117 | "cell_type": "markdown", 118 | "metadata": {}, 119 | "source": [ 120 | "## raw vs. cleaned result" 121 | ] 122 | }, 123 | { 124 | "cell_type": "code", 125 | "execution_count": 5, 126 | "metadata": {}, 127 | "outputs": [ 128 | { 129 | "name": "stdout", 130 | "output_type": "stream", 131 | "text": [ 132 | "!\n", 133 | " somthing is defined\n", 134 | "!\n", 135 | "interface Ethernet 0/1\n", 136 | " ip address dhcp\n", 137 | "!\n" 138 | ] 139 | } 140 | ], 141 | "source": [ 142 | "template = \"\"\"\\\n", 143 | "!\n", 144 | "{% if something %}\n", 145 | " somthing is defined\n", 146 | "{% endif %}\n", 147 | "!\n", 148 | "interface Ethernet 0/1\n", 149 | "{% if something_else %}\n", 150 | " {# use 5 blanks to maintain readablility in the clean output#}\n", 151 | " ip address dhcp\n", 152 | "{% endif %}\n", 153 | "!\"\"\"\n", 154 | "\n", 155 | "parameters = {\n", 156 | " \"something\": True,\n", 157 | " \"something_else\": True\n", 158 | "}\n", 159 | "result = confgen.render_from_string(template_content=template, parameters=parameters)\n", 160 | "\n", 161 | "# view the raw output\n", 162 | "print(result.template_result)" 163 | ] 164 | }, 165 | { 166 | "cell_type": "code", 167 | "execution_count": 6, 168 | "metadata": {}, 169 | "outputs": [ 170 | { 171 | "name": "stdout", 172 | "output_type": "stream", 173 | "text": [ 174 | "!\n", 175 | "somthing is defined\n", 176 | "!\n", 177 | "interface Ethernet 0/1\n", 178 | " ip address dhcp\n", 179 | "!\n" 180 | ] 181 | } 182 | ], 183 | "source": [ 184 | "print(result.cleaned_template_result())" 185 | ] 186 | }, 187 | { 188 | "cell_type": "markdown", 189 | "metadata": {}, 190 | "source": [ 191 | "## content error checks" 192 | ] 193 | }, 194 | { 195 | "cell_type": "code", 196 | "execution_count": 7, 197 | "metadata": {}, 198 | "outputs": [ 199 | { 200 | "data": { 201 | "text/plain": [ 202 | "False" 203 | ] 204 | }, 205 | "execution_count": 7, 206 | "metadata": {}, 207 | "output_type": "execute_result" 208 | } 209 | ], 210 | "source": [ 211 | "template = \"\"\"\\\n", 212 | "!\n", 213 | "hostname {{ hostname|default(_ERROR_.invalid_value) }}\n", 214 | "!\"\"\"\n", 215 | "\n", 216 | "result = confgen.render_from_string(template_content=template, parameters={})\n", 217 | "result.render_error" 218 | ] 219 | }, 220 | { 221 | "cell_type": "code", 222 | "execution_count": 8, 223 | "metadata": {}, 224 | "outputs": [ 225 | { 226 | "data": { 227 | "text/plain": [ 228 | "True" 229 | ] 230 | }, 231 | "execution_count": 8, 232 | "metadata": {}, 233 | "output_type": "execute_result" 234 | } 235 | ], 236 | "source": [ 237 | "result.content_error" 238 | ] 239 | }, 240 | { 241 | "cell_type": "markdown", 242 | "metadata": {}, 243 | "source": [ 244 | "## JSON format" 245 | ] 246 | }, 247 | { 248 | "cell_type": "code", 249 | "execution_count": 9, 250 | "metadata": {}, 251 | "outputs": [ 252 | { 253 | "data": { 254 | "text/plain": [ 255 | "{'content_error': True,\n", 256 | " 'error_text': None,\n", 257 | " 'from_string': True,\n", 258 | " 'render_error': False,\n", 259 | " 'search_path': None,\n", 260 | " 'template_file_name': None,\n", 261 | " 'template_result': '!\\nhostname $$INVALID_VALUE$$\\n!'}" 262 | ] 263 | }, 264 | "execution_count": 9, 265 | "metadata": {}, 266 | "output_type": "execute_result" 267 | } 268 | ], 269 | "source": [ 270 | "result.to_json()" 271 | ] 272 | }, 273 | { 274 | "cell_type": "code", 275 | "execution_count": null, 276 | "metadata": {}, 277 | "outputs": [], 278 | "source": [] 279 | } 280 | ], 281 | "metadata": { 282 | "kernelspec": { 283 | "display_name": "Python 3", 284 | "language": "python", 285 | "name": "python3" 286 | }, 287 | "language_info": { 288 | "codemirror_mode": { 289 | "name": "ipython", 290 | "version": 3 291 | }, 292 | "file_extension": ".py", 293 | "mimetype": "text/x-python", 294 | "name": "python", 295 | "nbconvert_exporter": "python", 296 | "pygments_lexer": "ipython3", 297 | "version": "3.5.4" 298 | } 299 | }, 300 | "nbformat": 4, 301 | "nbformat_minor": 2 302 | } 303 | -------------------------------------------------------------------------------- /notebooks/Introduction to the ipaddress module with IPv4.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# IPv4 addresses and networks in python 3\n", 8 | "\n", 9 | "This notebook provieds a brief overview of the ipaddress module, that was introduced starting with python version 3.3.\n", 10 | "\n", 11 | "Further details about this module can be found at the [official python documentation](https://docs.python.org/3.4/library/ipaddress.html).\n" 12 | ] 13 | }, 14 | { 15 | "cell_type": "code", 16 | "execution_count": 1, 17 | "metadata": { 18 | "collapsed": true 19 | }, 20 | "outputs": [], 21 | "source": [ 22 | "import ipaddress\n", 23 | "from ipaddress import IPv4Address, IPv4Network, IPv4Interface" 24 | ] 25 | }, 26 | { 27 | "cell_type": "markdown", 28 | "metadata": {}, 29 | "source": [ 30 | "## creating IPv4 objects using the factory methods\n", 31 | "\n", 32 | "The ipaddress module defines some factory functions, that can be used to create new instances for IPv4/IPv6 strings or integers.\n" 33 | ] 34 | }, 35 | { 36 | "cell_type": "code", 37 | "execution_count": 2, 38 | "metadata": { 39 | "collapsed": false 40 | }, 41 | "outputs": [ 42 | { 43 | "data": { 44 | "text/plain": [ 45 | "IPv4Address('10.10.10.128')" 46 | ] 47 | }, 48 | "execution_count": 2, 49 | "metadata": {}, 50 | "output_type": "execute_result" 51 | } 52 | ], 53 | "source": [ 54 | "ipaddress.ip_address(\"10.10.10.128\")" 55 | ] 56 | }, 57 | { 58 | "cell_type": "code", 59 | "execution_count": 3, 60 | "metadata": { 61 | "collapsed": false 62 | }, 63 | "outputs": [ 64 | { 65 | "data": { 66 | "text/plain": [ 67 | "IPv4Network('10.10.10.0/24')" 68 | ] 69 | }, 70 | "execution_count": 3, 71 | "metadata": {}, 72 | "output_type": "execute_result" 73 | } 74 | ], 75 | "source": [ 76 | "ipaddress.ip_network(\"10.10.10.0/24\")" 77 | ] 78 | }, 79 | { 80 | "cell_type": "code", 81 | "execution_count": 4, 82 | "metadata": { 83 | "collapsed": false 84 | }, 85 | "outputs": [ 86 | { 87 | "data": { 88 | "text/plain": [ 89 | "IPv4Network('10.10.10.0/24')" 90 | ] 91 | }, 92 | "execution_count": 4, 93 | "metadata": {}, 94 | "output_type": "execute_result" 95 | } 96 | ], 97 | "source": [ 98 | "ipaddress.ip_network(\"10.10.10.0/255.255.255.0\")" 99 | ] 100 | }, 101 | { 102 | "cell_type": "markdown", 103 | "metadata": {}, 104 | "source": [ 105 | "If you try to create an IPv4Network instance from a string a `ValueError` is thrown if the host bits are set. If you suspect, that a string will contain such a value, you can disable the `strict option`. Without the `strict` option, the factory function will ignore any host bit that is set." 106 | ] 107 | }, 108 | { 109 | "cell_type": "code", 110 | "execution_count": 5, 111 | "metadata": { 112 | "collapsed": false 113 | }, 114 | "outputs": [ 115 | { 116 | "data": { 117 | "text/plain": [ 118 | "IPv4Network('10.10.10.0/24')" 119 | ] 120 | }, 121 | "execution_count": 5, 122 | "metadata": {}, 123 | "output_type": "execute_result" 124 | } 125 | ], 126 | "source": [ 127 | "ipaddress.ip_network(\"10.10.10.5/24\", strict=False)" 128 | ] 129 | }, 130 | { 131 | "cell_type": "markdown", 132 | "metadata": {}, 133 | "source": [ 134 | "Another way to handle this situation is to use the `IPv4Interface` class. This class has the benefit, that you can extract the `IPv4Address` and the `IPv4Network` objects from a single instance." 135 | ] 136 | }, 137 | { 138 | "cell_type": "code", 139 | "execution_count": 6, 140 | "metadata": { 141 | "collapsed": false 142 | }, 143 | "outputs": [ 144 | { 145 | "data": { 146 | "text/plain": [ 147 | "IPv4Interface('10.10.10.5/24')" 148 | ] 149 | }, 150 | "execution_count": 6, 151 | "metadata": {}, 152 | "output_type": "execute_result" 153 | } 154 | ], 155 | "source": [ 156 | "intf = ipaddress.ip_interface(\"10.10.10.5/24\")\n", 157 | "intf" 158 | ] 159 | }, 160 | { 161 | "cell_type": "code", 162 | "execution_count": 7, 163 | "metadata": { 164 | "collapsed": false 165 | }, 166 | "outputs": [ 167 | { 168 | "data": { 169 | "text/plain": [ 170 | "IPv4Address('10.10.10.5')" 171 | ] 172 | }, 173 | "execution_count": 7, 174 | "metadata": {}, 175 | "output_type": "execute_result" 176 | } 177 | ], 178 | "source": [ 179 | "intf.ip" 180 | ] 181 | }, 182 | { 183 | "cell_type": "code", 184 | "execution_count": 8, 185 | "metadata": { 186 | "collapsed": false 187 | }, 188 | "outputs": [ 189 | { 190 | "data": { 191 | "text/plain": [ 192 | "IPv4Network('10.10.10.0/24')" 193 | ] 194 | }, 195 | "execution_count": 8, 196 | "metadata": {}, 197 | "output_type": "execute_result" 198 | } 199 | ], 200 | "source": [ 201 | "intf.network" 202 | ] 203 | }, 204 | { 205 | "cell_type": "markdown", 206 | "metadata": {}, 207 | "source": [ 208 | "## gather information from the IPv4Network object\n", 209 | "\n", 210 | "Within this notebook, I'll like to continue with the `IPv4Interface` class. This class provides the ability to create the `IPv4Address` or `IPv4Network` object if required, as shown above.\n" 211 | ] 212 | }, 213 | { 214 | "cell_type": "code", 215 | "execution_count": 9, 216 | "metadata": { 217 | "collapsed": false 218 | }, 219 | "outputs": [ 220 | { 221 | "data": { 222 | "text/plain": [ 223 | "4" 224 | ] 225 | }, 226 | "execution_count": 9, 227 | "metadata": {}, 228 | "output_type": "execute_result" 229 | } 230 | ], 231 | "source": [ 232 | "intf = ipaddress.ip_interface(\"10.10.10.5/24\")\n", 233 | "intf.version" 234 | ] 235 | }, 236 | { 237 | "cell_type": "markdown", 238 | "metadata": {}, 239 | "source": [ 240 | "get different representation of the IPv4 interface, address or network" 241 | ] 242 | }, 243 | { 244 | "cell_type": "code", 245 | "execution_count": 10, 246 | "metadata": { 247 | "collapsed": false 248 | }, 249 | "outputs": [ 250 | { 251 | "data": { 252 | "text/plain": [ 253 | "'10.10.10.5/255.255.255.0'" 254 | ] 255 | }, 256 | "execution_count": 10, 257 | "metadata": {}, 258 | "output_type": "execute_result" 259 | } 260 | ], 261 | "source": [ 262 | "intf.with_netmask" 263 | ] 264 | }, 265 | { 266 | "cell_type": "code", 267 | "execution_count": 11, 268 | "metadata": { 269 | "collapsed": false 270 | }, 271 | "outputs": [ 272 | { 273 | "data": { 274 | "text/plain": [ 275 | "'10.10.10.5/24'" 276 | ] 277 | }, 278 | "execution_count": 11, 279 | "metadata": {}, 280 | "output_type": "execute_result" 281 | } 282 | ], 283 | "source": [ 284 | "intf.with_prefixlen" 285 | ] 286 | }, 287 | { 288 | "cell_type": "code", 289 | "execution_count": 12, 290 | "metadata": { 291 | "collapsed": false 292 | }, 293 | "outputs": [ 294 | { 295 | "data": { 296 | "text/plain": [ 297 | "'10.10.10.0/255.255.255.0'" 298 | ] 299 | }, 300 | "execution_count": 12, 301 | "metadata": {}, 302 | "output_type": "execute_result" 303 | } 304 | ], 305 | "source": [ 306 | "intf.network.with_netmask" 307 | ] 308 | }, 309 | { 310 | "cell_type": "code", 311 | "execution_count": 13, 312 | "metadata": { 313 | "collapsed": false 314 | }, 315 | "outputs": [ 316 | { 317 | "data": { 318 | "text/plain": [ 319 | "'10.10.10.5/0.0.0.255'" 320 | ] 321 | }, 322 | "execution_count": 13, 323 | "metadata": {}, 324 | "output_type": "execute_result" 325 | } 326 | ], 327 | "source": [ 328 | "intf.with_hostmask" 329 | ] 330 | }, 331 | { 332 | "cell_type": "markdown", 333 | "metadata": {}, 334 | "source": [ 335 | "get information about the IPv4 interface, address and network" 336 | ] 337 | }, 338 | { 339 | "cell_type": "code", 340 | "execution_count": 14, 341 | "metadata": { 342 | "collapsed": false 343 | }, 344 | "outputs": [ 345 | { 346 | "data": { 347 | "text/plain": [ 348 | "True" 349 | ] 350 | }, 351 | "execution_count": 14, 352 | "metadata": {}, 353 | "output_type": "execute_result" 354 | } 355 | ], 356 | "source": [ 357 | "intf.network.is_private" 358 | ] 359 | }, 360 | { 361 | "cell_type": "code", 362 | "execution_count": 15, 363 | "metadata": { 364 | "collapsed": false 365 | }, 366 | "outputs": [ 367 | { 368 | "data": { 369 | "text/plain": [ 370 | "False" 371 | ] 372 | }, 373 | "execution_count": 15, 374 | "metadata": {}, 375 | "output_type": "execute_result" 376 | } 377 | ], 378 | "source": [ 379 | "intf.network.is_reserved" 380 | ] 381 | }, 382 | { 383 | "cell_type": "code", 384 | "execution_count": 16, 385 | "metadata": { 386 | "collapsed": false 387 | }, 388 | "outputs": [ 389 | { 390 | "data": { 391 | "text/plain": [ 392 | "False" 393 | ] 394 | }, 395 | "execution_count": 16, 396 | "metadata": {}, 397 | "output_type": "execute_result" 398 | } 399 | ], 400 | "source": [ 401 | "intf.network.is_global" 402 | ] 403 | }, 404 | { 405 | "cell_type": "code", 406 | "execution_count": 17, 407 | "metadata": { 408 | "collapsed": false 409 | }, 410 | "outputs": [ 411 | { 412 | "data": { 413 | "text/plain": [ 414 | "False" 415 | ] 416 | }, 417 | "execution_count": 17, 418 | "metadata": {}, 419 | "output_type": "execute_result" 420 | } 421 | ], 422 | "source": [ 423 | "intf.ip.is_multicast" 424 | ] 425 | }, 426 | { 427 | "cell_type": "code", 428 | "execution_count": 18, 429 | "metadata": { 430 | "collapsed": false 431 | }, 432 | "outputs": [ 433 | { 434 | "data": { 435 | "text/plain": [ 436 | "IPv4Address('10.10.10.255')" 437 | ] 438 | }, 439 | "execution_count": 18, 440 | "metadata": {}, 441 | "output_type": "execute_result" 442 | } 443 | ], 444 | "source": [ 445 | "intf.network.broadcast_address" 446 | ] 447 | }, 448 | { 449 | "cell_type": "code", 450 | "execution_count": 19, 451 | "metadata": { 452 | "collapsed": false 453 | }, 454 | "outputs": [ 455 | { 456 | "data": { 457 | "text/plain": [ 458 | "IPv4Address('10.10.10.0')" 459 | ] 460 | }, 461 | "execution_count": 19, 462 | "metadata": {}, 463 | "output_type": "execute_result" 464 | } 465 | ], 466 | "source": [ 467 | "intf.network.network_address" 468 | ] 469 | }, 470 | { 471 | "cell_type": "code", 472 | "execution_count": 20, 473 | "metadata": { 474 | "collapsed": false 475 | }, 476 | "outputs": [ 477 | { 478 | "data": { 479 | "text/plain": [ 480 | "256" 481 | ] 482 | }, 483 | "execution_count": 20, 484 | "metadata": {}, 485 | "output_type": "execute_result" 486 | } 487 | ], 488 | "source": [ 489 | "intf.network.num_addresses" 490 | ] 491 | }, 492 | { 493 | "cell_type": "code", 494 | "execution_count": 21, 495 | "metadata": { 496 | "collapsed": false 497 | }, 498 | "outputs": [ 499 | { 500 | "data": { 501 | "text/plain": [ 502 | "[IPv4Address('10.1.1.1'),\n", 503 | " IPv4Address('10.1.1.2'),\n", 504 | " IPv4Address('10.1.1.3'),\n", 505 | " IPv4Address('10.1.1.4'),\n", 506 | " IPv4Address('10.1.1.5'),\n", 507 | " IPv4Address('10.1.1.6')]" 508 | ] 509 | }, 510 | "execution_count": 21, 511 | "metadata": {}, 512 | "output_type": "execute_result" 513 | } 514 | ], 515 | "source": [ 516 | "list(IPv4Network(\"10.1.1.0/29\").hosts())" 517 | ] 518 | }, 519 | { 520 | "cell_type": "markdown", 521 | "metadata": {}, 522 | "source": [ 523 | "## Check if an IPv4 address is part of a network" 524 | ] 525 | }, 526 | { 527 | "cell_type": "code", 528 | "execution_count": 22, 529 | "metadata": { 530 | "collapsed": false 531 | }, 532 | "outputs": [ 533 | { 534 | "data": { 535 | "text/plain": [ 536 | "True" 537 | ] 538 | }, 539 | "execution_count": 22, 540 | "metadata": {}, 541 | "output_type": "execute_result" 542 | } 543 | ], 544 | "source": [ 545 | "ipaddr = IPv4Address(\"192.168.1.23\")\n", 546 | "\n", 547 | "ipaddr in IPv4Network(\"192.168.1.0/24\")" 548 | ] 549 | }, 550 | { 551 | "cell_type": "code", 552 | "execution_count": 23, 553 | "metadata": { 554 | "collapsed": false 555 | }, 556 | "outputs": [ 557 | { 558 | "data": { 559 | "text/plain": [ 560 | "False" 561 | ] 562 | }, 563 | "execution_count": 23, 564 | "metadata": {}, 565 | "output_type": "execute_result" 566 | } 567 | ], 568 | "source": [ 569 | "ipaddr in IPv4Network(\"192.168.2.0/24\")" 570 | ] 571 | }, 572 | { 573 | "cell_type": "markdown", 574 | "metadata": {}, 575 | "source": [ 576 | "## Subnetting in python\n", 577 | "\n", 578 | "The `ipaddress` module includes various functions to create subnets and supernets, to check whether a network overlaps or not etc.\n", 579 | "\n", 580 | "This notebook demonstrates just some basic functions that are required from day to day, further details can be found in the [official python documentation](https://docs.python.org/3.4/library/ipaddress.html).\n" 581 | ] 582 | }, 583 | { 584 | "cell_type": "code", 585 | "execution_count": 24, 586 | "metadata": { 587 | "collapsed": false, 588 | "scrolled": true 589 | }, 590 | "outputs": [ 591 | { 592 | "data": { 593 | "text/plain": [ 594 | "[IPv4Network('10.1.0.0/20'),\n", 595 | " IPv4Network('10.1.16.0/20'),\n", 596 | " IPv4Network('10.1.32.0/20'),\n", 597 | " IPv4Network('10.1.48.0/20'),\n", 598 | " IPv4Network('10.1.64.0/20'),\n", 599 | " IPv4Network('10.1.80.0/20'),\n", 600 | " IPv4Network('10.1.96.0/20'),\n", 601 | " IPv4Network('10.1.112.0/20'),\n", 602 | " IPv4Network('10.1.128.0/20'),\n", 603 | " IPv4Network('10.1.144.0/20'),\n", 604 | " IPv4Network('10.1.160.0/20'),\n", 605 | " IPv4Network('10.1.176.0/20'),\n", 606 | " IPv4Network('10.1.192.0/20'),\n", 607 | " IPv4Network('10.1.208.0/20'),\n", 608 | " IPv4Network('10.1.224.0/20'),\n", 609 | " IPv4Network('10.1.240.0/20')]" 610 | ] 611 | }, 612 | "execution_count": 24, 613 | "metadata": {}, 614 | "output_type": "execute_result" 615 | } 616 | ], 617 | "source": [ 618 | "ipnet = IPv4Network(\"10.1.0.0/16\")\n", 619 | "\n", 620 | "# prefixlen_diff = number of additional network bits\n", 621 | "list(ipnet.subnets(prefixlen_diff=4))" 622 | ] 623 | }, 624 | { 625 | "cell_type": "code", 626 | "execution_count": 25, 627 | "metadata": { 628 | "collapsed": false 629 | }, 630 | "outputs": [ 631 | { 632 | "data": { 633 | "text/plain": [ 634 | "[IPv4Network('10.1.0.0/20'),\n", 635 | " IPv4Network('10.1.16.0/20'),\n", 636 | " IPv4Network('10.1.32.0/20'),\n", 637 | " IPv4Network('10.1.48.0/20'),\n", 638 | " IPv4Network('10.1.64.0/20'),\n", 639 | " IPv4Network('10.1.80.0/20'),\n", 640 | " IPv4Network('10.1.96.0/20'),\n", 641 | " IPv4Network('10.1.112.0/20'),\n", 642 | " IPv4Network('10.1.128.0/20'),\n", 643 | " IPv4Network('10.1.144.0/20'),\n", 644 | " IPv4Network('10.1.160.0/20'),\n", 645 | " IPv4Network('10.1.176.0/20'),\n", 646 | " IPv4Network('10.1.192.0/20'),\n", 647 | " IPv4Network('10.1.208.0/20'),\n", 648 | " IPv4Network('10.1.224.0/20'),\n", 649 | " IPv4Network('10.1.240.0/20')]" 650 | ] 651 | }, 652 | "execution_count": 25, 653 | "metadata": {}, 654 | "output_type": "execute_result" 655 | } 656 | ], 657 | "source": [ 658 | "# new_prefix = number of network bits for the new prefix\n", 659 | "list(ipnet.subnets(new_prefix=20))" 660 | ] 661 | }, 662 | { 663 | "cell_type": "code", 664 | "execution_count": null, 665 | "metadata": { 666 | "collapsed": true 667 | }, 668 | "outputs": [], 669 | "source": [] 670 | } 671 | ], 672 | "metadata": { 673 | "kernelspec": { 674 | "display_name": "Python 3", 675 | "language": "python", 676 | "name": "python3" 677 | }, 678 | "language_info": { 679 | "codemirror_mode": { 680 | "name": "ipython", 681 | "version": 3 682 | }, 683 | "file_extension": ".py", 684 | "mimetype": "text/x-python", 685 | "name": "python", 686 | "nbconvert_exporter": "python", 687 | "pygments_lexer": "ipython3", 688 | "version": "3.4.3" 689 | } 690 | }, 691 | "nbformat": 4, 692 | "nbformat_minor": 0 693 | } 694 | -------------------------------------------------------------------------------- /notebooks/Introduction to the ipaddress module with IPv6.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# IPv6 addresses and networks in python 3\n", 8 | "\n", 9 | "This notebook provieds a brief overview of the ipaddress module, that was introduced starting with python version 3.3.\n", 10 | "\n", 11 | "Further details about this module can be found at the [official python documentation](https://docs.python.org/3.4/library/ipaddress.html).\n" 12 | ] 13 | }, 14 | { 15 | "cell_type": "code", 16 | "execution_count": 1, 17 | "metadata": { 18 | "collapsed": true 19 | }, 20 | "outputs": [], 21 | "source": [ 22 | "import ipaddress\n", 23 | "from ipaddress import IPv6Address, IPv6Network, IPv6Interface" 24 | ] 25 | }, 26 | { 27 | "cell_type": "markdown", 28 | "metadata": {}, 29 | "source": [ 30 | "## creating IPv6 addresses using the factory methods\n", 31 | "\n", 32 | "The ipaddress module defines some factory functions, that can be used to create new instances for IPv4/IPv6 strings or integers.\n" 33 | ] 34 | }, 35 | { 36 | "cell_type": "code", 37 | "execution_count": 2, 38 | "metadata": { 39 | "collapsed": false 40 | }, 41 | "outputs": [ 42 | { 43 | "data": { 44 | "text/plain": [ 45 | "IPv6Address('2001:db8::beef')" 46 | ] 47 | }, 48 | "execution_count": 2, 49 | "metadata": {}, 50 | "output_type": "execute_result" 51 | } 52 | ], 53 | "source": [ 54 | "ipaddress.ip_address(\"2001:0db8::beef\")" 55 | ] 56 | }, 57 | { 58 | "cell_type": "code", 59 | "execution_count": 3, 60 | "metadata": { 61 | "collapsed": false 62 | }, 63 | "outputs": [ 64 | { 65 | "data": { 66 | "text/plain": [ 67 | "IPv6Network('2001:db8:1::/64')" 68 | ] 69 | }, 70 | "execution_count": 3, 71 | "metadata": {}, 72 | "output_type": "execute_result" 73 | } 74 | ], 75 | "source": [ 76 | "ipaddress.ip_network(\"2001:0db8:1::/64\")" 77 | ] 78 | }, 79 | { 80 | "cell_type": "markdown", 81 | "metadata": {}, 82 | "source": [ 83 | "If you try to create an IPv6Network instance from a string a `ValueError` is thrown if the host bits are set. If you suspect, that a string will contain such a value, you can disable the `strict option`. Without the `strict` option, the factory function will ignore any host bit that is set." 84 | ] 85 | }, 86 | { 87 | "cell_type": "code", 88 | "execution_count": 4, 89 | "metadata": { 90 | "collapsed": false, 91 | "scrolled": true 92 | }, 93 | "outputs": [ 94 | { 95 | "data": { 96 | "text/plain": [ 97 | "IPv6Network('2001:db8:1::/64')" 98 | ] 99 | }, 100 | "execution_count": 4, 101 | "metadata": {}, 102 | "output_type": "execute_result" 103 | } 104 | ], 105 | "source": [ 106 | "ipaddress.ip_network(\"2001:0db8:1::affe/64\", strict=False)" 107 | ] 108 | }, 109 | { 110 | "cell_type": "markdown", 111 | "metadata": {}, 112 | "source": [ 113 | "Another way to handle this situation is to use the `IPv6Interface` class. This class has the benefit, that you can extract the `IPv6Address` and the `IPv6Network` objects from a single instance." 114 | ] 115 | }, 116 | { 117 | "cell_type": "code", 118 | "execution_count": 5, 119 | "metadata": { 120 | "collapsed": false 121 | }, 122 | "outputs": [ 123 | { 124 | "data": { 125 | "text/plain": [ 126 | "IPv6Interface('2001:db8:1::affe/64')" 127 | ] 128 | }, 129 | "execution_count": 5, 130 | "metadata": {}, 131 | "output_type": "execute_result" 132 | } 133 | ], 134 | "source": [ 135 | "intf = ipaddress.ip_interface(\"2001:0db8:1::affe/64\")\n", 136 | "intf" 137 | ] 138 | }, 139 | { 140 | "cell_type": "code", 141 | "execution_count": 6, 142 | "metadata": { 143 | "collapsed": false 144 | }, 145 | "outputs": [ 146 | { 147 | "data": { 148 | "text/plain": [ 149 | "IPv6Address('2001:db8:1::affe')" 150 | ] 151 | }, 152 | "execution_count": 6, 153 | "metadata": {}, 154 | "output_type": "execute_result" 155 | } 156 | ], 157 | "source": [ 158 | "intf.ip" 159 | ] 160 | }, 161 | { 162 | "cell_type": "code", 163 | "execution_count": 7, 164 | "metadata": { 165 | "collapsed": false 166 | }, 167 | "outputs": [ 168 | { 169 | "data": { 170 | "text/plain": [ 171 | "IPv6Network('2001:db8:1::/64')" 172 | ] 173 | }, 174 | "execution_count": 7, 175 | "metadata": {}, 176 | "output_type": "execute_result" 177 | } 178 | ], 179 | "source": [ 180 | "intf.network" 181 | ] 182 | }, 183 | { 184 | "cell_type": "markdown", 185 | "metadata": {}, 186 | "source": [ 187 | "## Display IPv6 addresses\n" 188 | ] 189 | }, 190 | { 191 | "cell_type": "code", 192 | "execution_count": 8, 193 | "metadata": { 194 | "collapsed": false 195 | }, 196 | "outputs": [ 197 | { 198 | "data": { 199 | "text/plain": [ 200 | "'2001:0db8:0001:0000:0000:0000:0000:affe/64'" 201 | ] 202 | }, 203 | "execution_count": 8, 204 | "metadata": {}, 205 | "output_type": "execute_result" 206 | } 207 | ], 208 | "source": [ 209 | "# display the long representation of the IPv6 address\n", 210 | "intf.exploded" 211 | ] 212 | }, 213 | { 214 | "cell_type": "code", 215 | "execution_count": 9, 216 | "metadata": { 217 | "collapsed": false 218 | }, 219 | "outputs": [ 220 | { 221 | "data": { 222 | "text/plain": [ 223 | "'2001:db8:32:0:beef:123:cafe:bd1'" 224 | ] 225 | }, 226 | "execution_count": 9, 227 | "metadata": {}, 228 | "output_type": "execute_result" 229 | } 230 | ], 231 | "source": [ 232 | "# display the short version of the IPv6 address\n", 233 | "IPv6Address(\"2001:0db8:0032:0000:beef:0123:cafe:0bd1\").compressed" 234 | ] 235 | }, 236 | { 237 | "cell_type": "markdown", 238 | "metadata": {}, 239 | "source": [ 240 | "## gather information from the IPv6Network object\n", 241 | "\n", 242 | "Within this notebook, I'll like to continue with the `IPv6Interface` class. This class provides the ability to create the `IPv6Address` or `IPv6Network` object if required, as shown above.\n" 243 | ] 244 | }, 245 | { 246 | "cell_type": "code", 247 | "execution_count": 10, 248 | "metadata": { 249 | "collapsed": false 250 | }, 251 | "outputs": [ 252 | { 253 | "data": { 254 | "text/plain": [ 255 | "6" 256 | ] 257 | }, 258 | "execution_count": 10, 259 | "metadata": {}, 260 | "output_type": "execute_result" 261 | } 262 | ], 263 | "source": [ 264 | "intf = ipaddress.ip_interface(\"2001:0db8:1::affe/64\")\n", 265 | "intf.version" 266 | ] 267 | }, 268 | { 269 | "cell_type": "markdown", 270 | "metadata": {}, 271 | "source": [ 272 | "get different representation of the IPv4 interface, address or network" 273 | ] 274 | }, 275 | { 276 | "cell_type": "code", 277 | "execution_count": 11, 278 | "metadata": { 279 | "collapsed": false 280 | }, 281 | "outputs": [ 282 | { 283 | "data": { 284 | "text/plain": [ 285 | "'2001:db8:1::affe/ffff:ffff:ffff:ffff::'" 286 | ] 287 | }, 288 | "execution_count": 11, 289 | "metadata": {}, 290 | "output_type": "execute_result" 291 | } 292 | ], 293 | "source": [ 294 | "intf.with_netmask" 295 | ] 296 | }, 297 | { 298 | "cell_type": "code", 299 | "execution_count": 12, 300 | "metadata": { 301 | "collapsed": false 302 | }, 303 | "outputs": [ 304 | { 305 | "data": { 306 | "text/plain": [ 307 | "'2001:db8:1::affe/64'" 308 | ] 309 | }, 310 | "execution_count": 12, 311 | "metadata": {}, 312 | "output_type": "execute_result" 313 | } 314 | ], 315 | "source": [ 316 | "intf.with_prefixlen" 317 | ] 318 | }, 319 | { 320 | "cell_type": "code", 321 | "execution_count": 13, 322 | "metadata": { 323 | "collapsed": false 324 | }, 325 | "outputs": [ 326 | { 327 | "data": { 328 | "text/plain": [ 329 | "'2001:db8:1::/ffff:ffff:ffff:ffff::'" 330 | ] 331 | }, 332 | "execution_count": 13, 333 | "metadata": {}, 334 | "output_type": "execute_result" 335 | } 336 | ], 337 | "source": [ 338 | "intf.network.with_netmask" 339 | ] 340 | }, 341 | { 342 | "cell_type": "code", 343 | "execution_count": 14, 344 | "metadata": { 345 | "collapsed": false 346 | }, 347 | "outputs": [ 348 | { 349 | "data": { 350 | "text/plain": [ 351 | "'2001:db8:1::affe/::ffff:ffff:ffff:ffff'" 352 | ] 353 | }, 354 | "execution_count": 14, 355 | "metadata": {}, 356 | "output_type": "execute_result" 357 | } 358 | ], 359 | "source": [ 360 | "intf.with_hostmask" 361 | ] 362 | }, 363 | { 364 | "cell_type": "markdown", 365 | "metadata": {}, 366 | "source": [ 367 | "get information about the IPv6 interface, address and network" 368 | ] 369 | }, 370 | { 371 | "cell_type": "code", 372 | "execution_count": 15, 373 | "metadata": { 374 | "collapsed": false 375 | }, 376 | "outputs": [ 377 | { 378 | "data": { 379 | "text/plain": [ 380 | "True" 381 | ] 382 | }, 383 | "execution_count": 15, 384 | "metadata": {}, 385 | "output_type": "execute_result" 386 | } 387 | ], 388 | "source": [ 389 | "intf.network.is_private" 390 | ] 391 | }, 392 | { 393 | "cell_type": "code", 394 | "execution_count": 16, 395 | "metadata": { 396 | "collapsed": false 397 | }, 398 | "outputs": [ 399 | { 400 | "data": { 401 | "text/plain": [ 402 | "False" 403 | ] 404 | }, 405 | "execution_count": 16, 406 | "metadata": {}, 407 | "output_type": "execute_result" 408 | } 409 | ], 410 | "source": [ 411 | "intf.network.is_reserved" 412 | ] 413 | }, 414 | { 415 | "cell_type": "code", 416 | "execution_count": 17, 417 | "metadata": { 418 | "collapsed": false 419 | }, 420 | "outputs": [ 421 | { 422 | "data": { 423 | "text/plain": [ 424 | "False" 425 | ] 426 | }, 427 | "execution_count": 17, 428 | "metadata": {}, 429 | "output_type": "execute_result" 430 | } 431 | ], 432 | "source": [ 433 | "intf.network.is_global" 434 | ] 435 | }, 436 | { 437 | "cell_type": "code", 438 | "execution_count": 18, 439 | "metadata": { 440 | "collapsed": false 441 | }, 442 | "outputs": [ 443 | { 444 | "data": { 445 | "text/plain": [ 446 | "False" 447 | ] 448 | }, 449 | "execution_count": 18, 450 | "metadata": {}, 451 | "output_type": "execute_result" 452 | } 453 | ], 454 | "source": [ 455 | "intf.network.is_multicast" 456 | ] 457 | }, 458 | { 459 | "cell_type": "code", 460 | "execution_count": 19, 461 | "metadata": { 462 | "collapsed": false 463 | }, 464 | "outputs": [ 465 | { 466 | "data": { 467 | "text/plain": [ 468 | "IPv6Address('2001:db8:1:0:ffff:ffff:ffff:ffff')" 469 | ] 470 | }, 471 | "execution_count": 19, 472 | "metadata": {}, 473 | "output_type": "execute_result" 474 | } 475 | ], 476 | "source": [ 477 | "intf.network.broadcast_address" 478 | ] 479 | }, 480 | { 481 | "cell_type": "code", 482 | "execution_count": 20, 483 | "metadata": { 484 | "collapsed": false 485 | }, 486 | "outputs": [ 487 | { 488 | "data": { 489 | "text/plain": [ 490 | "IPv6Address('2001:db8:1::')" 491 | ] 492 | }, 493 | "execution_count": 20, 494 | "metadata": {}, 495 | "output_type": "execute_result" 496 | } 497 | ], 498 | "source": [ 499 | "intf.network.network_address" 500 | ] 501 | }, 502 | { 503 | "cell_type": "code", 504 | "execution_count": 21, 505 | "metadata": { 506 | "collapsed": false 507 | }, 508 | "outputs": [ 509 | { 510 | "data": { 511 | "text/plain": [ 512 | "18446744073709551616" 513 | ] 514 | }, 515 | "execution_count": 21, 516 | "metadata": {}, 517 | "output_type": "execute_result" 518 | } 519 | ], 520 | "source": [ 521 | "intf.network.num_addresses" 522 | ] 523 | }, 524 | { 525 | "cell_type": "code", 526 | "execution_count": 22, 527 | "metadata": { 528 | "collapsed": false 529 | }, 530 | "outputs": [ 531 | { 532 | "data": { 533 | "text/plain": [ 534 | "[IPv6Address('2001:db8:0:1::1'),\n", 535 | " IPv6Address('2001:db8:0:1::2'),\n", 536 | " IPv6Address('2001:db8:0:1::3'),\n", 537 | " IPv6Address('2001:db8:0:1::4'),\n", 538 | " IPv6Address('2001:db8:0:1::5'),\n", 539 | " IPv6Address('2001:db8:0:1::6'),\n", 540 | " IPv6Address('2001:db8:0:1::7')]" 541 | ] 542 | }, 543 | "execution_count": 22, 544 | "metadata": {}, 545 | "output_type": "execute_result" 546 | } 547 | ], 548 | "source": [ 549 | "# be careful with the following function and /64 prefixes because of the large amount of addresses\n", 550 | "list(IPv6Network(\"2001:db8:0:1::/125\").hosts())" 551 | ] 552 | }, 553 | { 554 | "cell_type": "markdown", 555 | "metadata": {}, 556 | "source": [ 557 | "## Check if an IPv6 address is part of a network\n" 558 | ] 559 | }, 560 | { 561 | "cell_type": "code", 562 | "execution_count": 23, 563 | "metadata": { 564 | "collapsed": false 565 | }, 566 | "outputs": [ 567 | { 568 | "data": { 569 | "text/plain": [ 570 | "True" 571 | ] 572 | }, 573 | "execution_count": 23, 574 | "metadata": {}, 575 | "output_type": "execute_result" 576 | } 577 | ], 578 | "source": [ 579 | "ipaddr = IPv6Address(\"2001:db8:0:1::beef\")\n", 580 | "\n", 581 | "ipaddr in IPv6Network(\"2001:db8:0:1::/64\")" 582 | ] 583 | }, 584 | { 585 | "cell_type": "code", 586 | "execution_count": 24, 587 | "metadata": { 588 | "collapsed": false 589 | }, 590 | "outputs": [ 591 | { 592 | "data": { 593 | "text/plain": [ 594 | "False" 595 | ] 596 | }, 597 | "execution_count": 24, 598 | "metadata": {}, 599 | "output_type": "execute_result" 600 | } 601 | ], 602 | "source": [ 603 | "ipaddr in IPv6Network(\"2001:db8:0:2::/64\")" 604 | ] 605 | }, 606 | { 607 | "cell_type": "markdown", 608 | "metadata": {}, 609 | "source": [ 610 | "## Subnetting in python\n", 611 | "\n", 612 | "The `ipaddress` module includes various functions to create subnets and supernets, to check whether a network overlaps or not etc.\n", 613 | "\n", 614 | "This notebook demonstrates just some basic functions that are required from day to day, further details can be found in the [official python documentation](https://docs.python.org/3.4/library/ipaddress.html)." 615 | ] 616 | }, 617 | { 618 | "cell_type": "code", 619 | "execution_count": 25, 620 | "metadata": { 621 | "collapsed": false 622 | }, 623 | "outputs": [ 624 | { 625 | "data": { 626 | "text/plain": [ 627 | "[IPv6Network('2001:db8:0:1::/68'),\n", 628 | " IPv6Network('2001:db8:0:1:1000::/68'),\n", 629 | " IPv6Network('2001:db8:0:1:2000::/68'),\n", 630 | " IPv6Network('2001:db8:0:1:3000::/68'),\n", 631 | " IPv6Network('2001:db8:0:1:4000::/68'),\n", 632 | " IPv6Network('2001:db8:0:1:5000::/68'),\n", 633 | " IPv6Network('2001:db8:0:1:6000::/68'),\n", 634 | " IPv6Network('2001:db8:0:1:7000::/68'),\n", 635 | " IPv6Network('2001:db8:0:1:8000::/68'),\n", 636 | " IPv6Network('2001:db8:0:1:9000::/68'),\n", 637 | " IPv6Network('2001:db8:0:1:a000::/68'),\n", 638 | " IPv6Network('2001:db8:0:1:b000::/68'),\n", 639 | " IPv6Network('2001:db8:0:1:c000::/68'),\n", 640 | " IPv6Network('2001:db8:0:1:d000::/68'),\n", 641 | " IPv6Network('2001:db8:0:1:e000::/68'),\n", 642 | " IPv6Network('2001:db8:0:1:f000::/68')]" 643 | ] 644 | }, 645 | "execution_count": 25, 646 | "metadata": {}, 647 | "output_type": "execute_result" 648 | } 649 | ], 650 | "source": [ 651 | "ipnet = IPv6Network(\"2001:db8:0:1::/64\")\n", 652 | "\n", 653 | "# prefixlen_diff = number of additional network bits\n", 654 | "list(ipnet.subnets(prefixlen_diff=4))" 655 | ] 656 | }, 657 | { 658 | "cell_type": "code", 659 | "execution_count": 26, 660 | "metadata": { 661 | "collapsed": false 662 | }, 663 | "outputs": [ 664 | { 665 | "data": { 666 | "text/plain": [ 667 | "[IPv6Network('2001:db8:0:1::/68'),\n", 668 | " IPv6Network('2001:db8:0:1:1000::/68'),\n", 669 | " IPv6Network('2001:db8:0:1:2000::/68'),\n", 670 | " IPv6Network('2001:db8:0:1:3000::/68'),\n", 671 | " IPv6Network('2001:db8:0:1:4000::/68'),\n", 672 | " IPv6Network('2001:db8:0:1:5000::/68'),\n", 673 | " IPv6Network('2001:db8:0:1:6000::/68'),\n", 674 | " IPv6Network('2001:db8:0:1:7000::/68'),\n", 675 | " IPv6Network('2001:db8:0:1:8000::/68'),\n", 676 | " IPv6Network('2001:db8:0:1:9000::/68'),\n", 677 | " IPv6Network('2001:db8:0:1:a000::/68'),\n", 678 | " IPv6Network('2001:db8:0:1:b000::/68'),\n", 679 | " IPv6Network('2001:db8:0:1:c000::/68'),\n", 680 | " IPv6Network('2001:db8:0:1:d000::/68'),\n", 681 | " IPv6Network('2001:db8:0:1:e000::/68'),\n", 682 | " IPv6Network('2001:db8:0:1:f000::/68')]" 683 | ] 684 | }, 685 | "execution_count": 26, 686 | "metadata": {}, 687 | "output_type": "execute_result" 688 | } 689 | ], 690 | "source": [ 691 | "# new_prefix = number of network bits for the new prefix\n", 692 | "list(ipnet.subnets(new_prefix=68))" 693 | ] 694 | }, 695 | { 696 | "cell_type": "code", 697 | "execution_count": null, 698 | "metadata": { 699 | "collapsed": true 700 | }, 701 | "outputs": [], 702 | "source": [] 703 | } 704 | ], 705 | "metadata": { 706 | "kernelspec": { 707 | "display_name": "Python 3", 708 | "language": "python", 709 | "name": "python3" 710 | }, 711 | "language_info": { 712 | "codemirror_mode": { 713 | "name": "ipython", 714 | "version": 3 715 | }, 716 | "file_extension": ".py", 717 | "mimetype": "text/x-python", 718 | "name": "python", 719 | "nbconvert_exporter": "python", 720 | "pygments_lexer": "ipython3", 721 | "version": "3.4.3" 722 | } 723 | }, 724 | "nbformat": 4, 725 | "nbformat_minor": 0 726 | } 727 | -------------------------------------------------------------------------------- /notebooks/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hoelsner/python-script-examples/fb9753841f23c4b889395f8fc55988621b9dafa2/notebooks/__init__.py -------------------------------------------------------------------------------- /notebooks/import_test.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# import test notebook\n", 8 | "\n", 9 | "This notebook is only to test the import notebook function." 10 | ] 11 | }, 12 | { 13 | "cell_type": "code", 14 | "execution_count": 1, 15 | "metadata": { 16 | "collapsed": true 17 | }, 18 | "outputs": [], 19 | "source": [ 20 | "def echo_function(text):\n", 21 | " # simple echo function\n", 22 | " print(text)\n", 23 | " return text" 24 | ] 25 | } 26 | ], 27 | "metadata": { 28 | "kernelspec": { 29 | "display_name": "Python 3", 30 | "language": "python", 31 | "name": "python3" 32 | }, 33 | "language_info": { 34 | "codemirror_mode": { 35 | "name": "ipython", 36 | "version": 3 37 | }, 38 | "file_extension": ".py", 39 | "mimetype": "text/x-python", 40 | "name": "python", 41 | "nbconvert_exporter": "python", 42 | "pygments_lexer": "ipython3", 43 | "version": "3.4.3" 44 | } 45 | }, 46 | "nbformat": 4, 47 | "nbformat_minor": 0 48 | } 49 | -------------------------------------------------------------------------------- /notebooks/pandas/Merge DataFrames in Pandas.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Merge DataFrames in Pandas\n", 8 | "\n", 9 | "This notebooks shows how to read Pandas DataFrames from an Excel workbook and merge them based on specific rows. \n" 10 | ] 11 | }, 12 | { 13 | "cell_type": "code", 14 | "execution_count": 1, 15 | "metadata": { 16 | "collapsed": true 17 | }, 18 | "outputs": [], 19 | "source": [ 20 | "import json\n", 21 | "from pandas import DataFrame\n", 22 | "import pandas as pd\n", 23 | "import numpy as np" 24 | ] 25 | }, 26 | { 27 | "cell_type": "code", 28 | "execution_count": 2, 29 | "metadata": { 30 | "collapsed": true 31 | }, 32 | "outputs": [], 33 | "source": [ 34 | "EXCEL_SOURCE_FILE = \"example_workbook.xlsx\"" 35 | ] 36 | }, 37 | { 38 | "cell_type": "markdown", 39 | "metadata": {}, 40 | "source": [ 41 | "First, the interface data from the Excel sheet is loaded to a new pandas DataFrame. To verify that the DataFrame contains some data, the `head()` function is used. It shows the first five entries within the DataFrame.\n", 42 | "\n", 43 | "Additional information about the `read_excel()` method is available in the [Pandas documentation](http://pandas.pydata.org/pandas-docs/stable/generated/pandas.read_excel.html)." 44 | ] 45 | }, 46 | { 47 | "cell_type": "code", 48 | "execution_count": 3, 49 | "metadata": { 50 | "collapsed": false 51 | }, 52 | "outputs": [ 53 | { 54 | "data": { 55 | "text/html": [ 56 | "
\n", 57 | "\n", 58 | " \n", 59 | " \n", 60 | " \n", 61 | " \n", 62 | " \n", 63 | " \n", 64 | " \n", 65 | " \n", 66 | " \n", 67 | " \n", 68 | " \n", 69 | " \n", 70 | " \n", 71 | " \n", 72 | " \n", 73 | " \n", 74 | " \n", 75 | " \n", 76 | " \n", 77 | " \n", 78 | " \n", 79 | " \n", 80 | " \n", 81 | " \n", 82 | " \n", 83 | " \n", 84 | " \n", 85 | " \n", 86 | " \n", 87 | " \n", 88 | " \n", 89 | " \n", 90 | " \n", 91 | " \n", 92 | " \n", 93 | " \n", 94 | " \n", 95 | " \n", 96 | " \n", 97 | " \n", 98 | " \n", 99 | " \n", 100 | " \n", 101 | " \n", 102 | " \n", 103 | " \n", 104 | "
hostnameinterface_namedescriptionport_role
0Switch_Ag0/1Router PortRouter
1Switch_Ag0/2NaNData_Port
2Switch_Ag0/3NaNData_Port
3Switch_Ag0/4NaNData_Port
4Switch_Ag0/5NaNData_Port
\n", 105 | "
" 106 | ], 107 | "text/plain": [ 108 | " hostname interface_name description port_role\n", 109 | "0 Switch_A g0/1 Router Port Router\n", 110 | "1 Switch_A g0/2 NaN Data_Port\n", 111 | "2 Switch_A g0/3 NaN Data_Port\n", 112 | "3 Switch_A g0/4 NaN Data_Port\n", 113 | "4 Switch_A g0/5 NaN Data_Port" 114 | ] 115 | }, 116 | "execution_count": 3, 117 | "metadata": {}, 118 | "output_type": "execute_result" 119 | } 120 | ], 121 | "source": [ 122 | "interface_df = pd.read_excel(EXCEL_SOURCE_FILE, sheetname=\"interface\")\n", 123 | "interface_df.head()" 124 | ] 125 | }, 126 | { 127 | "cell_type": "markdown", 128 | "metadata": {}, 129 | "source": [ 130 | "The DataFrame should now be extended with information from the `port_role` sheet. " 131 | ] 132 | }, 133 | { 134 | "cell_type": "code", 135 | "execution_count": 4, 136 | "metadata": { 137 | "collapsed": false 138 | }, 139 | "outputs": [ 140 | { 141 | "data": { 142 | "text/html": [ 143 | "
\n", 144 | "\n", 145 | " \n", 146 | " \n", 147 | " \n", 148 | " \n", 149 | " \n", 150 | " \n", 151 | " \n", 152 | " \n", 153 | " \n", 154 | " \n", 155 | " \n", 156 | " \n", 157 | " \n", 158 | " \n", 159 | " \n", 160 | " \n", 161 | " \n", 162 | " \n", 163 | " \n", 164 | " \n", 165 | " \n", 166 | " \n", 167 | " \n", 168 | " \n", 169 | " \n", 170 | " \n", 171 | " \n", 172 | " \n", 173 | " \n", 174 | " \n", 175 | " \n", 176 | " \n", 177 | " \n", 178 | " \n", 179 | " \n", 180 | " \n", 181 | " \n", 182 | " \n", 183 | " \n", 184 | " \n", 185 | " \n", 186 | " \n", 187 | " \n", 188 | " \n", 189 | " \n", 190 | " \n", 191 | "
nameAccess VLAN IDVoice VLAN IDTrunk VLAN List
0Data_Port10NaNNaN
1Voice_Port1020.0NaN
2Switch_to_Switch1NaN1-100
3Router80NaNNaN
4Printer_Port11NaNNaN
\n", 192 | "
" 193 | ], 194 | "text/plain": [ 195 | " name Access VLAN ID Voice VLAN ID Trunk VLAN List\n", 196 | "0 Data_Port 10 NaN NaN\n", 197 | "1 Voice_Port 10 20.0 NaN\n", 198 | "2 Switch_to_Switch 1 NaN 1-100\n", 199 | "3 Router 80 NaN NaN\n", 200 | "4 Printer_Port 11 NaN NaN" 201 | ] 202 | }, 203 | "execution_count": 4, 204 | "metadata": {}, 205 | "output_type": "execute_result" 206 | } 207 | ], 208 | "source": [ 209 | "port_role_df = pd.read_excel(EXCEL_SOURCE_FILE, sheetname=\"port_role\")\n", 210 | "port_role_df.head()" 211 | ] 212 | }, 213 | { 214 | "cell_type": "markdown", 215 | "metadata": {}, 216 | "source": [ 217 | "Pandas provides a nice feature to merge data from two DataFrames by a specific column name. We have different key names in this example, therefore we need to specify the `left_on` and `right_on` parameter to identify the column that should be used for the merge operation.\n", 218 | "\n", 219 | "**Please Note:** The merge operation will create a new DataFrame that contains the values of the both DataFrames. The existing one won't be changed\n", 220 | "\n", 221 | "Further information about the merge function is available at [pandas documentation on merge](http://pandas.pydata.org/pandas-docs/stable/generated/pandas.DataFrame.merge.html)" 222 | ] 223 | }, 224 | { 225 | "cell_type": "code", 226 | "execution_count": 5, 227 | "metadata": { 228 | "collapsed": false 229 | }, 230 | "outputs": [ 231 | { 232 | "data": { 233 | "text/html": [ 234 | "
\n", 235 | "\n", 236 | " \n", 237 | " \n", 238 | " \n", 239 | " \n", 240 | " \n", 241 | " \n", 242 | " \n", 243 | " \n", 244 | " \n", 245 | " \n", 246 | " \n", 247 | " \n", 248 | " \n", 249 | " \n", 250 | " \n", 251 | " \n", 252 | " \n", 253 | " \n", 254 | " \n", 255 | " \n", 256 | " \n", 257 | " \n", 258 | " \n", 259 | " \n", 260 | " \n", 261 | " \n", 262 | " \n", 263 | " \n", 264 | " \n", 265 | " \n", 266 | " \n", 267 | " \n", 268 | " \n", 269 | " \n", 270 | " \n", 271 | " \n", 272 | " \n", 273 | " \n", 274 | " \n", 275 | " \n", 276 | " \n", 277 | " \n", 278 | " \n", 279 | " \n", 280 | " \n", 281 | " \n", 282 | " \n", 283 | " \n", 284 | " \n", 285 | " \n", 286 | " \n", 287 | " \n", 288 | " \n", 289 | " \n", 290 | " \n", 291 | " \n", 292 | " \n", 293 | " \n", 294 | " \n", 295 | " \n", 296 | " \n", 297 | " \n", 298 | " \n", 299 | " \n", 300 | " \n", 301 | " \n", 302 | " \n", 303 | " \n", 304 | " \n", 305 | " \n", 306 | " \n", 307 | " \n", 308 | " \n", 309 | " \n", 310 | " \n", 311 | " \n", 312 | " \n", 313 | " \n", 314 | " \n", 315 | " \n", 316 | " \n", 317 | " \n", 318 | " \n", 319 | " \n", 320 | " \n", 321 | " \n", 322 | " \n", 323 | " \n", 324 | " \n", 325 | " \n", 326 | " \n", 327 | " \n", 328 | " \n", 329 | " \n", 330 | " \n", 331 | " \n", 332 | " \n", 333 | " \n", 334 | " \n", 335 | " \n", 336 | " \n", 337 | " \n", 338 | " \n", 339 | " \n", 340 | " \n", 341 | " \n", 342 | " \n", 343 | " \n", 344 | " \n", 345 | " \n", 346 | " \n", 347 | " \n", 348 | " \n", 349 | " \n", 350 | " \n", 351 | " \n", 352 | " \n", 353 | " \n", 354 | " \n", 355 | " \n", 356 | " \n", 357 | " \n", 358 | " \n", 359 | " \n", 360 | " \n", 361 | "
hostnameinterface_namedescriptionport_rolenameAccess VLAN IDVoice VLAN IDTrunk VLAN List
0Switch_Ag0/1Router PortRouterRouter80NaNNaN
1Switch_Ag0/2NaNData_PortData_Port10NaNNaN
2Switch_Ag0/3NaNData_PortData_Port10NaNNaN
3Switch_Ag0/4NaNData_PortData_Port10NaNNaN
4Switch_Ag0/5NaNData_PortData_Port10NaNNaN
5Switch_Ag0/6NaNVoice_PortVoice_Port1020.0NaN
6Switch_Ag0/7NaNVoice_PortVoice_Port1020.0NaN
7Switch_Ag0/8NaNVoice_PortVoice_Port1020.0NaN
8Switch_Ag0/9NaNVoice_PortVoice_Port1020.0NaN
9Switch_Ag0/10NaNVoice_PortVoice_Port1020.0NaN
\n", 362 | "
" 363 | ], 364 | "text/plain": [ 365 | " hostname interface_name description port_role name \\\n", 366 | "0 Switch_A g0/1 Router Port Router Router \n", 367 | "1 Switch_A g0/2 NaN Data_Port Data_Port \n", 368 | "2 Switch_A g0/3 NaN Data_Port Data_Port \n", 369 | "3 Switch_A g0/4 NaN Data_Port Data_Port \n", 370 | "4 Switch_A g0/5 NaN Data_Port Data_Port \n", 371 | "5 Switch_A g0/6 NaN Voice_Port Voice_Port \n", 372 | "6 Switch_A g0/7 NaN Voice_Port Voice_Port \n", 373 | "7 Switch_A g0/8 NaN Voice_Port Voice_Port \n", 374 | "8 Switch_A g0/9 NaN Voice_Port Voice_Port \n", 375 | "9 Switch_A g0/10 NaN Voice_Port Voice_Port \n", 376 | "\n", 377 | " Access VLAN ID Voice VLAN ID Trunk VLAN List \n", 378 | "0 80 NaN NaN \n", 379 | "1 10 NaN NaN \n", 380 | "2 10 NaN NaN \n", 381 | "3 10 NaN NaN \n", 382 | "4 10 NaN NaN \n", 383 | "5 10 20.0 NaN \n", 384 | "6 10 20.0 NaN \n", 385 | "7 10 20.0 NaN \n", 386 | "8 10 20.0 NaN \n", 387 | "9 10 20.0 NaN " 388 | ] 389 | }, 390 | "execution_count": 5, 391 | "metadata": {}, 392 | "output_type": "execute_result" 393 | } 394 | ], 395 | "source": [ 396 | "full_intf_df = pd.merge(interface_df, port_role_df, left_on=\"port_role\", right_on=\"name\")\n", 397 | "full_intf_df.head(10)" 398 | ] 399 | }, 400 | { 401 | "cell_type": "markdown", 402 | "metadata": {}, 403 | "source": [ 404 | "The new DataFrame contains now the general interface information and the information from the `port_role` sheet. The `port_role` and `name` contains the same values, therefore one can be removed from the DataFrame." 405 | ] 406 | }, 407 | { 408 | "cell_type": "code", 409 | "execution_count": 6, 410 | "metadata": { 411 | "collapsed": false 412 | }, 413 | "outputs": [ 414 | { 415 | "data": { 416 | "text/html": [ 417 | "
\n", 418 | "\n", 419 | " \n", 420 | " \n", 421 | " \n", 422 | " \n", 423 | " \n", 424 | " \n", 425 | " \n", 426 | " \n", 427 | " \n", 428 | " \n", 429 | " \n", 430 | " \n", 431 | " \n", 432 | " \n", 433 | " \n", 434 | " \n", 435 | " \n", 436 | " \n", 437 | " \n", 438 | " \n", 439 | " \n", 440 | " \n", 441 | " \n", 442 | " \n", 443 | " \n", 444 | " \n", 445 | " \n", 446 | " \n", 447 | " \n", 448 | " \n", 449 | " \n", 450 | " \n", 451 | " \n", 452 | " \n", 453 | " \n", 454 | " \n", 455 | " \n", 456 | " \n", 457 | " \n", 458 | " \n", 459 | " \n", 460 | " \n", 461 | " \n", 462 | " \n", 463 | " \n", 464 | " \n", 465 | " \n", 466 | " \n", 467 | " \n", 468 | " \n", 469 | " \n", 470 | " \n", 471 | " \n", 472 | " \n", 473 | " \n", 474 | " \n", 475 | " \n", 476 | " \n", 477 | " \n", 478 | " \n", 479 | " \n", 480 | " \n", 481 | " \n", 482 | " \n", 483 | "
hostnameinterface_namedescriptionport_roleAccess VLAN IDVoice VLAN IDTrunk VLAN List
0Switch_Ag0/1Router PortRouter80NaNNaN
1Switch_Ag0/2NaNData_Port10NaNNaN
2Switch_Ag0/3NaNData_Port10NaNNaN
3Switch_Ag0/4NaNData_Port10NaNNaN
4Switch_Ag0/5NaNData_Port10NaNNaN
\n", 484 | "
" 485 | ], 486 | "text/plain": [ 487 | " hostname interface_name description port_role Access VLAN ID \\\n", 488 | "0 Switch_A g0/1 Router Port Router 80 \n", 489 | "1 Switch_A g0/2 NaN Data_Port 10 \n", 490 | "2 Switch_A g0/3 NaN Data_Port 10 \n", 491 | "3 Switch_A g0/4 NaN Data_Port 10 \n", 492 | "4 Switch_A g0/5 NaN Data_Port 10 \n", 493 | "\n", 494 | " Voice VLAN ID Trunk VLAN List \n", 495 | "0 NaN NaN \n", 496 | "1 NaN NaN \n", 497 | "2 NaN NaN \n", 498 | "3 NaN NaN \n", 499 | "4 NaN NaN " 500 | ] 501 | }, 502 | "execution_count": 6, 503 | "metadata": {}, 504 | "output_type": "execute_result" 505 | } 506 | ], 507 | "source": [ 508 | "del full_intf_df[\"name\"]\n", 509 | "full_intf_df.head(5)" 510 | ] 511 | }, 512 | { 513 | "cell_type": "markdown", 514 | "metadata": {}, 515 | "source": [ 516 | "For the next steps, only the interface information for `Switch_A` are used." 517 | ] 518 | }, 519 | { 520 | "cell_type": "code", 521 | "execution_count": 7, 522 | "metadata": { 523 | "collapsed": false 524 | }, 525 | "outputs": [ 526 | { 527 | "data": { 528 | "text/html": [ 529 | "
\n", 530 | "\n", 531 | " \n", 532 | " \n", 533 | " \n", 534 | " \n", 535 | " \n", 536 | " \n", 537 | " \n", 538 | " \n", 539 | " \n", 540 | " \n", 541 | " \n", 542 | " \n", 543 | " \n", 544 | " \n", 545 | " \n", 546 | " \n", 547 | " \n", 548 | " \n", 549 | " \n", 550 | " \n", 551 | " \n", 552 | " \n", 553 | " \n", 554 | " \n", 555 | " \n", 556 | " \n", 557 | " \n", 558 | " \n", 559 | " \n", 560 | " \n", 561 | " \n", 562 | " \n", 563 | " \n", 564 | " \n", 565 | " \n", 566 | " \n", 567 | " \n", 568 | " \n", 569 | " \n", 570 | " \n", 571 | " \n", 572 | " \n", 573 | " \n", 574 | " \n", 575 | " \n", 576 | " \n", 577 | " \n", 578 | " \n", 579 | " \n", 580 | " \n", 581 | " \n", 582 | " \n", 583 | " \n", 584 | " \n", 585 | " \n", 586 | " \n", 587 | " \n", 588 | " \n", 589 | " \n", 590 | " \n", 591 | " \n", 592 | " \n", 593 | " \n", 594 | " \n", 595 | " \n", 596 | " \n", 597 | " \n", 598 | " \n", 599 | " \n", 600 | " \n", 601 | " \n", 602 | " \n", 603 | " \n", 604 | " \n", 605 | " \n", 606 | " \n", 607 | " \n", 608 | " \n", 609 | " \n", 610 | " \n", 611 | " \n", 612 | " \n", 613 | " \n", 614 | " \n", 615 | " \n", 616 | " \n", 617 | " \n", 618 | " \n", 619 | " \n", 620 | " \n", 621 | " \n", 622 | " \n", 623 | " \n", 624 | " \n", 625 | " \n", 626 | " \n", 627 | " \n", 628 | " \n", 629 | " \n", 630 | " \n", 631 | " \n", 632 | " \n", 633 | " \n", 634 | " \n", 635 | " \n", 636 | " \n", 637 | " \n", 638 | " \n", 639 | " \n", 640 | " \n", 641 | " \n", 642 | " \n", 643 | " \n", 644 | " \n", 645 | " \n", 646 | " \n", 647 | " \n", 648 | " \n", 649 | " \n", 650 | " \n", 651 | " \n", 652 | " \n", 653 | " \n", 654 | " \n", 655 | " \n", 656 | " \n", 657 | " \n", 658 | " \n", 659 | " \n", 660 | " \n", 661 | " \n", 662 | " \n", 663 | " \n", 664 | " \n", 665 | " \n", 666 | " \n", 667 | " \n", 668 | " \n", 669 | " \n", 670 | " \n", 671 | " \n", 672 | " \n", 673 | " \n", 674 | " \n", 675 | "
hostnameinterface_namedescriptionport_roleAccess VLAN IDVoice VLAN IDTrunk VLAN List
0Switch_Ag0/1Router PortRouter80NaNNaN
1Switch_Ag0/2NaNData_Port10NaNNaN
2Switch_Ag0/3NaNData_Port10NaNNaN
3Switch_Ag0/4NaNData_Port10NaNNaN
4Switch_Ag0/5NaNData_Port10NaNNaN
5Switch_Ag0/6NaNVoice_Port1020.0NaN
6Switch_Ag0/7NaNVoice_Port1020.0NaN
7Switch_Ag0/8NaNVoice_Port1020.0NaN
8Switch_Ag0/9NaNVoice_Port1020.0NaN
9Switch_Ag0/10NaNVoice_Port1020.0NaN
22Switch_Ag0/14NaNPrinter_Port11NaNNaN
25Switch_Ag0/15Trunk to Switch BSwitch_to_Switch1NaN1-100
26Switch_Ag0/16Trunk to Switch CSwitch_to_Switch1NaN1-100
\n", 676 | "
" 677 | ], 678 | "text/plain": [ 679 | " hostname interface_name description port_role \\\n", 680 | "0 Switch_A g0/1 Router Port Router \n", 681 | "1 Switch_A g0/2 NaN Data_Port \n", 682 | "2 Switch_A g0/3 NaN Data_Port \n", 683 | "3 Switch_A g0/4 NaN Data_Port \n", 684 | "4 Switch_A g0/5 NaN Data_Port \n", 685 | "5 Switch_A g0/6 NaN Voice_Port \n", 686 | "6 Switch_A g0/7 NaN Voice_Port \n", 687 | "7 Switch_A g0/8 NaN Voice_Port \n", 688 | "8 Switch_A g0/9 NaN Voice_Port \n", 689 | "9 Switch_A g0/10 NaN Voice_Port \n", 690 | "22 Switch_A g0/14 NaN Printer_Port \n", 691 | "25 Switch_A g0/15 Trunk to Switch B Switch_to_Switch \n", 692 | "26 Switch_A g0/16 Trunk to Switch C Switch_to_Switch \n", 693 | "\n", 694 | " Access VLAN ID Voice VLAN ID Trunk VLAN List \n", 695 | "0 80 NaN NaN \n", 696 | "1 10 NaN NaN \n", 697 | "2 10 NaN NaN \n", 698 | "3 10 NaN NaN \n", 699 | "4 10 NaN NaN \n", 700 | "5 10 20.0 NaN \n", 701 | "6 10 20.0 NaN \n", 702 | "7 10 20.0 NaN \n", 703 | "8 10 20.0 NaN \n", 704 | "9 10 20.0 NaN \n", 705 | "22 11 NaN NaN \n", 706 | "25 1 NaN 1-100 \n", 707 | "26 1 NaN 1-100 " 708 | ] 709 | }, 710 | "execution_count": 7, 711 | "metadata": {}, 712 | "output_type": "execute_result" 713 | } 714 | ], 715 | "source": [ 716 | "switch_a_intf_df = full_intf_df[full_intf_df.hostname == \"Switch_A\"]\n", 717 | "switch_a_intf_df" 718 | ] 719 | }, 720 | { 721 | "cell_type": "markdown", 722 | "metadata": {}, 723 | "source": [ 724 | "The hostname is removed, because it's not required anymore." 725 | ] 726 | }, 727 | { 728 | "cell_type": "code", 729 | "execution_count": 8, 730 | "metadata": { 731 | "collapsed": false 732 | }, 733 | "outputs": [ 734 | { 735 | "data": { 736 | "text/html": [ 737 | "
\n", 738 | "\n", 739 | " \n", 740 | " \n", 741 | " \n", 742 | " \n", 743 | " \n", 744 | " \n", 745 | " \n", 746 | " \n", 747 | " \n", 748 | " \n", 749 | " \n", 750 | " \n", 751 | " \n", 752 | " \n", 753 | " \n", 754 | " \n", 755 | " \n", 756 | " \n", 757 | " \n", 758 | " \n", 759 | " \n", 760 | " \n", 761 | " \n", 762 | " \n", 763 | " \n", 764 | " \n", 765 | " \n", 766 | " \n", 767 | " \n", 768 | " \n", 769 | " \n", 770 | " \n", 771 | " \n", 772 | " \n", 773 | " \n", 774 | " \n", 775 | " \n", 776 | " \n", 777 | " \n", 778 | " \n", 779 | " \n", 780 | " \n", 781 | " \n", 782 | " \n", 783 | " \n", 784 | " \n", 785 | " \n", 786 | " \n", 787 | " \n", 788 | " \n", 789 | " \n", 790 | " \n", 791 | " \n", 792 | " \n", 793 | " \n", 794 | " \n", 795 | " \n", 796 | " \n", 797 | " \n", 798 | " \n", 799 | " \n", 800 | " \n", 801 | " \n", 802 | " \n", 803 | " \n", 804 | " \n", 805 | " \n", 806 | " \n", 807 | " \n", 808 | " \n", 809 | " \n", 810 | " \n", 811 | " \n", 812 | " \n", 813 | " \n", 814 | " \n", 815 | " \n", 816 | " \n", 817 | " \n", 818 | " \n", 819 | " \n", 820 | " \n", 821 | " \n", 822 | " \n", 823 | " \n", 824 | " \n", 825 | " \n", 826 | " \n", 827 | " \n", 828 | " \n", 829 | " \n", 830 | " \n", 831 | " \n", 832 | " \n", 833 | " \n", 834 | " \n", 835 | " \n", 836 | " \n", 837 | " \n", 838 | " \n", 839 | " \n", 840 | " \n", 841 | " \n", 842 | " \n", 843 | " \n", 844 | " \n", 845 | " \n", 846 | " \n", 847 | " \n", 848 | " \n", 849 | " \n", 850 | " \n", 851 | " \n", 852 | " \n", 853 | " \n", 854 | " \n", 855 | " \n", 856 | " \n", 857 | " \n", 858 | " \n", 859 | " \n", 860 | " \n", 861 | " \n", 862 | " \n", 863 | " \n", 864 | " \n", 865 | " \n", 866 | " \n", 867 | " \n", 868 | " \n", 869 | "
interface_namedescriptionport_roleAccess VLAN IDVoice VLAN IDTrunk VLAN List
0g0/1Router PortRouter80NaNNaN
1g0/2NaNData_Port10NaNNaN
2g0/3NaNData_Port10NaNNaN
3g0/4NaNData_Port10NaNNaN
4g0/5NaNData_Port10NaNNaN
5g0/6NaNVoice_Port1020.0NaN
6g0/7NaNVoice_Port1020.0NaN
7g0/8NaNVoice_Port1020.0NaN
8g0/9NaNVoice_Port1020.0NaN
9g0/10NaNVoice_Port1020.0NaN
22g0/14NaNPrinter_Port11NaNNaN
25g0/15Trunk to Switch BSwitch_to_Switch1NaN1-100
26g0/16Trunk to Switch CSwitch_to_Switch1NaN1-100
\n", 870 | "
" 871 | ], 872 | "text/plain": [ 873 | " interface_name description port_role Access VLAN ID \\\n", 874 | "0 g0/1 Router Port Router 80 \n", 875 | "1 g0/2 NaN Data_Port 10 \n", 876 | "2 g0/3 NaN Data_Port 10 \n", 877 | "3 g0/4 NaN Data_Port 10 \n", 878 | "4 g0/5 NaN Data_Port 10 \n", 879 | "5 g0/6 NaN Voice_Port 10 \n", 880 | "6 g0/7 NaN Voice_Port 10 \n", 881 | "7 g0/8 NaN Voice_Port 10 \n", 882 | "8 g0/9 NaN Voice_Port 10 \n", 883 | "9 g0/10 NaN Voice_Port 10 \n", 884 | "22 g0/14 NaN Printer_Port 11 \n", 885 | "25 g0/15 Trunk to Switch B Switch_to_Switch 1 \n", 886 | "26 g0/16 Trunk to Switch C Switch_to_Switch 1 \n", 887 | "\n", 888 | " Voice VLAN ID Trunk VLAN List \n", 889 | "0 NaN NaN \n", 890 | "1 NaN NaN \n", 891 | "2 NaN NaN \n", 892 | "3 NaN NaN \n", 893 | "4 NaN NaN \n", 894 | "5 20.0 NaN \n", 895 | "6 20.0 NaN \n", 896 | "7 20.0 NaN \n", 897 | "8 20.0 NaN \n", 898 | "9 20.0 NaN \n", 899 | "22 NaN NaN \n", 900 | "25 NaN 1-100 \n", 901 | "26 NaN 1-100 " 902 | ] 903 | }, 904 | "execution_count": 8, 905 | "metadata": {}, 906 | "output_type": "execute_result" 907 | } 908 | ], 909 | "source": [ 910 | "del switch_a_intf_df[\"hostname\"]\n", 911 | "switch_a_intf_df" 912 | ] 913 | }, 914 | { 915 | "cell_type": "markdown", 916 | "metadata": {}, 917 | "source": [ 918 | "The column names are directly used from the Excel sheet. To use them, for example in configuration templates, we need to clean the column names (remove the blanks and make them uppercase). The following code renames all columns." 919 | ] 920 | }, 921 | { 922 | "cell_type": "code", 923 | "execution_count": 9, 924 | "metadata": { 925 | "collapsed": false 926 | }, 927 | "outputs": [ 928 | { 929 | "data": { 930 | "text/html": [ 931 | "
\n", 932 | "\n", 933 | " \n", 934 | " \n", 935 | " \n", 936 | " \n", 937 | " \n", 938 | " \n", 939 | " \n", 940 | " \n", 941 | " \n", 942 | " \n", 943 | " \n", 944 | " \n", 945 | " \n", 946 | " \n", 947 | " \n", 948 | " \n", 949 | " \n", 950 | " \n", 951 | " \n", 952 | " \n", 953 | " \n", 954 | " \n", 955 | " \n", 956 | " \n", 957 | " \n", 958 | " \n", 959 | " \n", 960 | " \n", 961 | " \n", 962 | " \n", 963 | " \n", 964 | " \n", 965 | " \n", 966 | " \n", 967 | " \n", 968 | " \n", 969 | " \n", 970 | " \n", 971 | " \n", 972 | " \n", 973 | " \n", 974 | " \n", 975 | " \n", 976 | " \n", 977 | " \n", 978 | " \n", 979 | " \n", 980 | " \n", 981 | " \n", 982 | " \n", 983 | " \n", 984 | " \n", 985 | " \n", 986 | " \n", 987 | " \n", 988 | " \n", 989 | " \n", 990 | " \n", 991 | " \n", 992 | " \n", 993 | " \n", 994 | " \n", 995 | " \n", 996 | " \n", 997 | " \n", 998 | " \n", 999 | " \n", 1000 | " \n", 1001 | " \n", 1002 | " \n", 1003 | " \n", 1004 | " \n", 1005 | " \n", 1006 | " \n", 1007 | " \n", 1008 | " \n", 1009 | " \n", 1010 | " \n", 1011 | " \n", 1012 | " \n", 1013 | " \n", 1014 | " \n", 1015 | " \n", 1016 | " \n", 1017 | " \n", 1018 | " \n", 1019 | " \n", 1020 | " \n", 1021 | " \n", 1022 | " \n", 1023 | " \n", 1024 | " \n", 1025 | " \n", 1026 | " \n", 1027 | " \n", 1028 | " \n", 1029 | " \n", 1030 | " \n", 1031 | " \n", 1032 | " \n", 1033 | " \n", 1034 | " \n", 1035 | " \n", 1036 | " \n", 1037 | " \n", 1038 | " \n", 1039 | " \n", 1040 | " \n", 1041 | " \n", 1042 | " \n", 1043 | " \n", 1044 | " \n", 1045 | " \n", 1046 | " \n", 1047 | " \n", 1048 | " \n", 1049 | " \n", 1050 | " \n", 1051 | " \n", 1052 | " \n", 1053 | " \n", 1054 | " \n", 1055 | " \n", 1056 | " \n", 1057 | " \n", 1058 | " \n", 1059 | " \n", 1060 | " \n", 1061 | " \n", 1062 | " \n", 1063 | "
INTERFACE_NAMEDESCRIPTIONPORT_ROLEACCESS_VLAN_IDVOICE_VLAN_IDTRUNK_VLAN_LIST
0g0/1Router PortRouter80NaNNaN
1g0/2NaNData_Port10NaNNaN
2g0/3NaNData_Port10NaNNaN
3g0/4NaNData_Port10NaNNaN
4g0/5NaNData_Port10NaNNaN
5g0/6NaNVoice_Port1020.0NaN
6g0/7NaNVoice_Port1020.0NaN
7g0/8NaNVoice_Port1020.0NaN
8g0/9NaNVoice_Port1020.0NaN
9g0/10NaNVoice_Port1020.0NaN
22g0/14NaNPrinter_Port11NaNNaN
25g0/15Trunk to Switch BSwitch_to_Switch1NaN1-100
26g0/16Trunk to Switch CSwitch_to_Switch1NaN1-100
\n", 1064 | "
" 1065 | ], 1066 | "text/plain": [ 1067 | " INTERFACE_NAME DESCRIPTION PORT_ROLE ACCESS_VLAN_ID \\\n", 1068 | "0 g0/1 Router Port Router 80 \n", 1069 | "1 g0/2 NaN Data_Port 10 \n", 1070 | "2 g0/3 NaN Data_Port 10 \n", 1071 | "3 g0/4 NaN Data_Port 10 \n", 1072 | "4 g0/5 NaN Data_Port 10 \n", 1073 | "5 g0/6 NaN Voice_Port 10 \n", 1074 | "6 g0/7 NaN Voice_Port 10 \n", 1075 | "7 g0/8 NaN Voice_Port 10 \n", 1076 | "8 g0/9 NaN Voice_Port 10 \n", 1077 | "9 g0/10 NaN Voice_Port 10 \n", 1078 | "22 g0/14 NaN Printer_Port 11 \n", 1079 | "25 g0/15 Trunk to Switch B Switch_to_Switch 1 \n", 1080 | "26 g0/16 Trunk to Switch C Switch_to_Switch 1 \n", 1081 | "\n", 1082 | " VOICE_VLAN_ID TRUNK_VLAN_LIST \n", 1083 | "0 NaN NaN \n", 1084 | "1 NaN NaN \n", 1085 | "2 NaN NaN \n", 1086 | "3 NaN NaN \n", 1087 | "4 NaN NaN \n", 1088 | "5 20.0 NaN \n", 1089 | "6 20.0 NaN \n", 1090 | "7 20.0 NaN \n", 1091 | "8 20.0 NaN \n", 1092 | "9 20.0 NaN \n", 1093 | "22 NaN NaN \n", 1094 | "25 NaN 1-100 \n", 1095 | "26 NaN 1-100 " 1096 | ] 1097 | }, 1098 | "execution_count": 9, 1099 | "metadata": {}, 1100 | "output_type": "execute_result" 1101 | } 1102 | ], 1103 | "source": [ 1104 | "# we will use a list comprehension fot this\n", 1105 | "column_replacements = dict(\n", 1106 | " zip(\n", 1107 | " switch_a_intf_df.columns, # the current column names\n", 1108 | " [e.upper().replace(\" \", \"_\") for e in switch_a_intf_df.columns] # the new column names\n", 1109 | " )\n", 1110 | ")\n", 1111 | "switch_a_intf_df = switch_a_intf_df.rename(columns=column_replacements)\n", 1112 | "switch_a_intf_df" 1113 | ] 1114 | }, 1115 | { 1116 | "cell_type": "markdown", 1117 | "metadata": {}, 1118 | "source": [ 1119 | "The `NaN` values are present if the DataFrame is converted to a string representation. To replace these values with an empty string, use the following `fillna()` function." 1120 | ] 1121 | }, 1122 | { 1123 | "cell_type": "code", 1124 | "execution_count": 10, 1125 | "metadata": { 1126 | "collapsed": false 1127 | }, 1128 | "outputs": [ 1129 | { 1130 | "data": { 1131 | "text/html": [ 1132 | "
\n", 1133 | "\n", 1134 | " \n", 1135 | " \n", 1136 | " \n", 1137 | " \n", 1138 | " \n", 1139 | " \n", 1140 | " \n", 1141 | " \n", 1142 | " \n", 1143 | " \n", 1144 | " \n", 1145 | " \n", 1146 | " \n", 1147 | " \n", 1148 | " \n", 1149 | " \n", 1150 | " \n", 1151 | " \n", 1152 | " \n", 1153 | " \n", 1154 | " \n", 1155 | " \n", 1156 | " \n", 1157 | " \n", 1158 | " \n", 1159 | " \n", 1160 | " \n", 1161 | " \n", 1162 | " \n", 1163 | " \n", 1164 | " \n", 1165 | " \n", 1166 | " \n", 1167 | " \n", 1168 | " \n", 1169 | " \n", 1170 | " \n", 1171 | " \n", 1172 | " \n", 1173 | " \n", 1174 | " \n", 1175 | " \n", 1176 | " \n", 1177 | " \n", 1178 | " \n", 1179 | " \n", 1180 | " \n", 1181 | " \n", 1182 | " \n", 1183 | " \n", 1184 | " \n", 1185 | " \n", 1186 | " \n", 1187 | " \n", 1188 | " \n", 1189 | " \n", 1190 | " \n", 1191 | " \n", 1192 | " \n", 1193 | " \n", 1194 | " \n", 1195 | " \n", 1196 | " \n", 1197 | " \n", 1198 | " \n", 1199 | " \n", 1200 | " \n", 1201 | " \n", 1202 | " \n", 1203 | " \n", 1204 | " \n", 1205 | " \n", 1206 | " \n", 1207 | " \n", 1208 | " \n", 1209 | " \n", 1210 | " \n", 1211 | " \n", 1212 | " \n", 1213 | " \n", 1214 | " \n", 1215 | " \n", 1216 | " \n", 1217 | " \n", 1218 | " \n", 1219 | " \n", 1220 | " \n", 1221 | " \n", 1222 | " \n", 1223 | " \n", 1224 | " \n", 1225 | " \n", 1226 | " \n", 1227 | " \n", 1228 | " \n", 1229 | " \n", 1230 | " \n", 1231 | " \n", 1232 | " \n", 1233 | " \n", 1234 | " \n", 1235 | " \n", 1236 | " \n", 1237 | " \n", 1238 | " \n", 1239 | " \n", 1240 | " \n", 1241 | " \n", 1242 | " \n", 1243 | " \n", 1244 | " \n", 1245 | " \n", 1246 | " \n", 1247 | " \n", 1248 | " \n", 1249 | " \n", 1250 | " \n", 1251 | " \n", 1252 | " \n", 1253 | " \n", 1254 | " \n", 1255 | " \n", 1256 | " \n", 1257 | " \n", 1258 | " \n", 1259 | " \n", 1260 | " \n", 1261 | " \n", 1262 | " \n", 1263 | " \n", 1264 | "
INTERFACE_NAMEDESCRIPTIONPORT_ROLEACCESS_VLAN_IDVOICE_VLAN_IDTRUNK_VLAN_LIST
0g0/1Router PortRouter80
1g0/2Data_Port10
2g0/3Data_Port10
3g0/4Data_Port10
4g0/5Data_Port10
5g0/6Voice_Port1020
6g0/7Voice_Port1020
7g0/8Voice_Port1020
8g0/9Voice_Port1020
9g0/10Voice_Port1020
22g0/14Printer_Port11
25g0/15Trunk to Switch BSwitch_to_Switch11-100
26g0/16Trunk to Switch CSwitch_to_Switch11-100
\n", 1265 | "
" 1266 | ], 1267 | "text/plain": [ 1268 | " INTERFACE_NAME DESCRIPTION PORT_ROLE ACCESS_VLAN_ID \\\n", 1269 | "0 g0/1 Router Port Router 80 \n", 1270 | "1 g0/2 Data_Port 10 \n", 1271 | "2 g0/3 Data_Port 10 \n", 1272 | "3 g0/4 Data_Port 10 \n", 1273 | "4 g0/5 Data_Port 10 \n", 1274 | "5 g0/6 Voice_Port 10 \n", 1275 | "6 g0/7 Voice_Port 10 \n", 1276 | "7 g0/8 Voice_Port 10 \n", 1277 | "8 g0/9 Voice_Port 10 \n", 1278 | "9 g0/10 Voice_Port 10 \n", 1279 | "22 g0/14 Printer_Port 11 \n", 1280 | "25 g0/15 Trunk to Switch B Switch_to_Switch 1 \n", 1281 | "26 g0/16 Trunk to Switch C Switch_to_Switch 1 \n", 1282 | "\n", 1283 | " VOICE_VLAN_ID TRUNK_VLAN_LIST \n", 1284 | "0 \n", 1285 | "1 \n", 1286 | "2 \n", 1287 | "3 \n", 1288 | "4 \n", 1289 | "5 20 \n", 1290 | "6 20 \n", 1291 | "7 20 \n", 1292 | "8 20 \n", 1293 | "9 20 \n", 1294 | "22 \n", 1295 | "25 1-100 \n", 1296 | "26 1-100 " 1297 | ] 1298 | }, 1299 | "execution_count": 10, 1300 | "metadata": {}, 1301 | "output_type": "execute_result" 1302 | } 1303 | ], 1304 | "source": [ 1305 | "clean_list = switch_a_intf_df.fillna(\"\")\n", 1306 | "clean_list" 1307 | ] 1308 | }, 1309 | { 1310 | "cell_type": "markdown", 1311 | "metadata": {}, 1312 | "source": [ 1313 | "Now, every row is converted to a `dictionary` and added to another dictionary that can be used, e.g. with Jinja2 to generate a configurations. I wrote another article about the [configuration generation with python and Jinja2](https://codingnetworker.com/2015/09/configuration-generator-with-python-and-jinja2/)." 1314 | ] 1315 | }, 1316 | { 1317 | "cell_type": "code", 1318 | "execution_count": 11, 1319 | "metadata": { 1320 | "collapsed": false 1321 | }, 1322 | "outputs": [ 1323 | { 1324 | "name": "stdout", 1325 | "output_type": "stream", 1326 | "text": [ 1327 | "{\n", 1328 | " \"HOSTNAME\": \"Switch A\",\n", 1329 | " \"PORTS\": [\n", 1330 | " {\n", 1331 | " \"DESCRIPTION\": \"Router Port\",\n", 1332 | " \"ACCESS_VLAN_ID\": 80,\n", 1333 | " \"INTERFACE_NAME\": \"g0/1\",\n", 1334 | " \"TRUNK_VLAN_LIST\": \"\",\n", 1335 | " \"VOICE_VLAN_ID\": \"\",\n", 1336 | " \"PORT_ROLE\": \"Router\"\n", 1337 | " },\n", 1338 | " {\n", 1339 | " \"DESCRIPTION\": \"\",\n", 1340 | " \"ACCESS_VLAN_ID\": 10,\n", 1341 | " \"INTERFACE_NAME\": \"g0/2\",\n", 1342 | " \"TRUNK_VLAN_LIST\": \"\",\n", 1343 | " \"VOICE_VLAN_ID\": \"\",\n", 1344 | " \"PORT_ROLE\": \"Data_Port\"\n", 1345 | " },\n", 1346 | " {\n", 1347 | " \"DESCRIPTION\": \"\",\n", 1348 | " \"ACCESS_VLAN_ID\": 10,\n", 1349 | " \"INTERFACE_NAME\": \"g0/3\",\n", 1350 | " \"TRUNK_VLAN_LIST\": \"\",\n", 1351 | " \"VOICE_VLAN_ID\": \"\",\n", 1352 | " \"PORT_ROLE\": \"Data_Port\"\n", 1353 | " },\n", 1354 | " {\n", 1355 | " \"DESCRIPTION\": \"\",\n", 1356 | " \"ACCESS_VLAN_ID\": 10,\n", 1357 | " \"INTERFACE_NAME\": \"g0/4\",\n", 1358 | " \"TRUNK_VLAN_LIST\": \"\",\n", 1359 | " \"VOICE_VLAN_ID\": \"\",\n", 1360 | " \"PORT_ROLE\": \"Data_Port\"\n", 1361 | " },\n", 1362 | " {\n", 1363 | " \"DESCRIPTION\": \"\",\n", 1364 | " \"ACCESS_VLAN_ID\": 10,\n", 1365 | " \"INTERFACE_NAME\": \"g0/5\",\n", 1366 | " \"TRUNK_VLAN_LIST\": \"\",\n", 1367 | " \"VOICE_VLAN_ID\": \"\",\n", 1368 | " \"PORT_ROLE\": \"Data_Port\"\n", 1369 | " },\n", 1370 | " {\n", 1371 | " \"DESCRIPTION\": \"\",\n", 1372 | " \"ACCESS_VLAN_ID\": 10,\n", 1373 | " \"INTERFACE_NAME\": \"g0/6\",\n", 1374 | " \"TRUNK_VLAN_LIST\": \"\",\n", 1375 | " \"VOICE_VLAN_ID\": 20.0,\n", 1376 | " \"PORT_ROLE\": \"Voice_Port\"\n", 1377 | " },\n", 1378 | " {\n", 1379 | " \"DESCRIPTION\": \"\",\n", 1380 | " \"ACCESS_VLAN_ID\": 10,\n", 1381 | " \"INTERFACE_NAME\": \"g0/7\",\n", 1382 | " \"TRUNK_VLAN_LIST\": \"\",\n", 1383 | " \"VOICE_VLAN_ID\": 20.0,\n", 1384 | " \"PORT_ROLE\": \"Voice_Port\"\n", 1385 | " },\n", 1386 | " {\n", 1387 | " \"DESCRIPTION\": \"\",\n", 1388 | " \"ACCESS_VLAN_ID\": 10,\n", 1389 | " \"INTERFACE_NAME\": \"g0/8\",\n", 1390 | " \"TRUNK_VLAN_LIST\": \"\",\n", 1391 | " \"VOICE_VLAN_ID\": 20.0,\n", 1392 | " \"PORT_ROLE\": \"Voice_Port\"\n", 1393 | " },\n", 1394 | " {\n", 1395 | " \"DESCRIPTION\": \"\",\n", 1396 | " \"ACCESS_VLAN_ID\": 10,\n", 1397 | " \"INTERFACE_NAME\": \"g0/9\",\n", 1398 | " \"TRUNK_VLAN_LIST\": \"\",\n", 1399 | " \"VOICE_VLAN_ID\": 20.0,\n", 1400 | " \"PORT_ROLE\": \"Voice_Port\"\n", 1401 | " },\n", 1402 | " {\n", 1403 | " \"DESCRIPTION\": \"\",\n", 1404 | " \"ACCESS_VLAN_ID\": 10,\n", 1405 | " \"INTERFACE_NAME\": \"g0/10\",\n", 1406 | " \"TRUNK_VLAN_LIST\": \"\",\n", 1407 | " \"VOICE_VLAN_ID\": 20.0,\n", 1408 | " \"PORT_ROLE\": \"Voice_Port\"\n", 1409 | " },\n", 1410 | " {\n", 1411 | " \"DESCRIPTION\": \"\",\n", 1412 | " \"ACCESS_VLAN_ID\": 11,\n", 1413 | " \"INTERFACE_NAME\": \"g0/14\",\n", 1414 | " \"TRUNK_VLAN_LIST\": \"\",\n", 1415 | " \"VOICE_VLAN_ID\": \"\",\n", 1416 | " \"PORT_ROLE\": \"Printer_Port\"\n", 1417 | " },\n", 1418 | " {\n", 1419 | " \"DESCRIPTION\": \"Trunk to Switch B\",\n", 1420 | " \"ACCESS_VLAN_ID\": 1,\n", 1421 | " \"INTERFACE_NAME\": \"g0/15\",\n", 1422 | " \"TRUNK_VLAN_LIST\": \"1-100\",\n", 1423 | " \"VOICE_VLAN_ID\": \"\",\n", 1424 | " \"PORT_ROLE\": \"Switch_to_Switch\"\n", 1425 | " },\n", 1426 | " {\n", 1427 | " \"DESCRIPTION\": \"Trunk to Switch C\",\n", 1428 | " \"ACCESS_VLAN_ID\": 1,\n", 1429 | " \"INTERFACE_NAME\": \"g0/16\",\n", 1430 | " \"TRUNK_VLAN_LIST\": \"1-100\",\n", 1431 | " \"VOICE_VLAN_ID\": \"\",\n", 1432 | " \"PORT_ROLE\": \"Switch_to_Switch\"\n", 1433 | " }\n", 1434 | " ]\n", 1435 | "}\n" 1436 | ] 1437 | } 1438 | ], 1439 | "source": [ 1440 | "result = {\n", 1441 | " \"HOSTNAME\": \"Switch A\", # we only have Switch A in this case\n", 1442 | " \"PORTS\": []\n", 1443 | "}\n", 1444 | "for index, row in clean_list.iterrows():\n", 1445 | " result[\"PORTS\"].append(row.to_dict())\n", 1446 | "\n", 1447 | "print(json.dumps(result, indent=4))" 1448 | ] 1449 | }, 1450 | { 1451 | "cell_type": "code", 1452 | "execution_count": null, 1453 | "metadata": { 1454 | "collapsed": true 1455 | }, 1456 | "outputs": [], 1457 | "source": [] 1458 | } 1459 | ], 1460 | "metadata": { 1461 | "kernelspec": { 1462 | "display_name": "Python 3", 1463 | "language": "python", 1464 | "name": "python3" 1465 | }, 1466 | "language_info": { 1467 | "codemirror_mode": { 1468 | "name": "ipython", 1469 | "version": 3 1470 | }, 1471 | "file_extension": ".py", 1472 | "mimetype": "text/x-python", 1473 | "name": "python", 1474 | "nbconvert_exporter": "python", 1475 | "pygments_lexer": "ipython3", 1476 | "version": "3.5.1" 1477 | } 1478 | }, 1479 | "nbformat": 4, 1480 | "nbformat_minor": 1 1481 | } 1482 | -------------------------------------------------------------------------------- /notebooks/pandas/example_workbook.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hoelsner/python-script-examples/fb9753841f23c4b889395f8fc55988621b9dafa2/notebooks/pandas/example_workbook.xlsx -------------------------------------------------------------------------------- /parse-show-inventory-with-textfsm/parse_show_inventory.py: -------------------------------------------------------------------------------- 1 | import jtextfsm as textfsm 2 | 3 | # Load the input file to a variable 4 | input_file = open("show_inventory.txt", encoding='utf-8') 5 | raw_text_data = input_file.read() 6 | input_file.close() 7 | 8 | # Run the text through the FSM. 9 | # The argument 'template' is a file handle and 'raw_text_data' is a 10 | # string with the content from the show_inventory.txt file 11 | template = open("show_inventory_multiple.textfsm") 12 | re_table = textfsm.TextFSM(template) 13 | fsm_results = re_table.ParseText(raw_text_data) 14 | 15 | # the results are written to a CSV file 16 | outfile_name = open("outfile.csv", "w+") 17 | outfile = outfile_name 18 | 19 | # Display result as CSV and write it to the output file 20 | # First the column headers... 21 | print(re_table.header) 22 | for s in re_table.header: 23 | outfile.write("%s;" % s) 24 | outfile.write("\n") 25 | 26 | # ...now all row's which were parsed by TextFSM 27 | counter = 0 28 | for row in fsm_results: 29 | print(row) 30 | for s in row: 31 | outfile.write("%s;" % s) 32 | outfile.write("\n") 33 | counter += 1 34 | print("Write %d records" % counter) 35 | -------------------------------------------------------------------------------- /parse-show-inventory-with-textfsm/show_inventory.txt: -------------------------------------------------------------------------------- 1 | switch-1001#sh inventory 2 | NAME: "1", DESCR: "WS-C2960-24TT-L" 3 | PID: WS-C2960-24TT-L , VID: V09 , SN: 12345ABCD 4 | --------------------------------------------------------------------------------------------------- 5 | switch-1002#sh inventory 6 | NAME: "1", DESCR: "WS-C2960-48TT-L" 7 | PID: WS-C2960-48TT-L , VID: V02 , SN: 12345ABCD 8 | --------------------------------------------------------------------------------------------------- 9 | switch-2001#sh inventory 10 | NAME: "1", DESCR: "WS-C3750X-24" 11 | PID: WS-C3750X-24T-S , VID: V04 , SN: 12345ABCD 12 | NAME: "Switch 1 - Power Supply 0", DESCR: "FRU Power Supply" 13 | PID: C3KX-PWR-350WAC , VID: V02 , SN: 12345ABCD 14 | NAME: "Switch 1 - FRULink Slot 1 - FRULink Module", DESCR: "FRULink 1G Module" 15 | PID: C3KX-NM-1G , VID: V01 , SN: 12345ABCD 16 | NAME: "GigabitEthernet1/1/1", DESCR: "1000BaseLX SFP" 17 | PID: Unspecified , VID: , SN: 12345ABCD 18 | NAME: "2", DESCR: "WS-C3750X-24" 19 | PID: WS-C3750X-24T-S , VID: V04 , SN: 12345ABCD 20 | NAME: "Switch 2 - Power Supply 0", DESCR: "FRU Power Supply" 21 | PID: C3KX-PWR-350WAC , VID: V02 , SN: 12345ABCD 22 | NAME: "Switch 2 - FRULink Slot 1 - FRULink Module", DESCR: "FRULink 1G Module" 23 | PID: C3KX-NM-1G , VID: V01 , SN: 12345ABCD 24 | NAME: "GigabitEthernet2/1/1", DESCR: "1000BaseLX SFP" 25 | PID: Unspecified , VID: , SN: 12345ABCD 26 | NAME: "3", DESCR: "WS-C3750X-24" 27 | PID: WS-C3750X-24T-S , VID: V04 , SN: 12345ABCD 28 | NAME: "Switch 3 - Power Supply 0", DESCR: "FRU Power Supply" 29 | PID: C3KX-PWR-350WAC , VID: V02 , SN: 12345ABCD 30 | NAME: "Switch 3 - FRULink Slot 1 - FRULink Module", DESCR: "FRULink 1G Module" 31 | PID: C3KX-NM-1G , VID: V01 , SN: 12345ABCD 32 | NAME: "GigabitEthernet3/1/1", DESCR: "1000BaseLX SFP" 33 | PID: Unspecified , VID: , SN: 12345ABCD 34 | NAME: "4", DESCR: "WS-C3750X-24" 35 | PID: WS-C3750X-24T-S , VID: V04 , SN: 12345ABCD 36 | NAME: "Switch 4 - Power Supply 0", DESCR: "FRU Power Supply" 37 | PID: C3KX-PWR-350WAC , VID: V02 , SN: 12345ABCD 38 | NAME: "Switch 4 - FRULink Slot 1 - FRULink Module", DESCR: "FRULink 1G Module" 39 | PID: C3KX-NM-1G , VID: V01 , SN: 12345ABCD 40 | NAME: "GigabitEthernet4/1/1", DESCR: "1000BaseLX SFP" 41 | PID: Unspecified , VID: , SN: 12345ABCD 42 | --------------------------------------------------------------------------------------------------- 43 | switch-2002#sh inventory 44 | NAME: "1", DESCR: "WS-C3560G-24TS" 45 | PID: WS-C3560G-24TS-S , VID: V03, SN: 12345ABCD 46 | NAME: "GigabitEthernet0/25", DESCR: "1000BaseSX SFP" 47 | PID: , VID: , SN: 12345ABCD 48 | NAME: "GigabitEthernet0/27", DESCR: "1000BaseSX SFP" 49 | PID: , VID: , SN: 12345ABCD 50 | NAME: "GigabitEthernet0/28", DESCR: "1000BaseSX SFP" 51 | PID: , VID: , SN: 12345ABCD 52 | --------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------- /parse-show-inventory-with-textfsm/show_inventory_multiple.textfsm: -------------------------------------------------------------------------------- 1 | Value Filldown hostname (\S+) 2 | Value name (.+) 3 | Value description (.*) 4 | Value productid (\S*) 5 | Value vid (\S*) 6 | Value Required serialnumber (\S+) 7 | 8 | Start 9 | ^${hostname}[>#].* 10 | ^NAME: "${name}", DESCR: "${description}" 11 | ^PID: ${productid}.*VID: ${vid}.*SN: ${serialnumber} -> Record -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | Jinja2 2 | MarkupSafe 3 | jtextfsm 4 | requests 5 | python-slugify 6 | psutil 7 | jupyter 8 | ciscoconfparse 9 | netmiko 10 | cerberus 11 | pandas 12 | lxml 13 | BeautifulSoup4 14 | html5lib 15 | networkconfgen -------------------------------------------------------------------------------- /setup.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | - hosts: localhost 3 | gather_facts: False 4 | tasks: 5 | - name: Install packages required to run the examples 6 | apt: pkg={{item}} state=installed 7 | sudo: true 8 | with_items: 9 | - python3.4 10 | - python-pip 11 | - python3-pip 12 | - ansible 13 | - git 14 | - build-essential 15 | - python3-dev 16 | - libssl-dev 17 | - libffi-dev 18 | - libxml2-dev 19 | - libxslt1-dev 20 | 21 | - name: install required python dependencies 22 | sudo: true 23 | pip: requirements=/vagrant/requirements.txt executable=pip3 24 | 25 | # create a symlink in the home directory to the shared directory 26 | - name: create 'examples' link 27 | file: src=/vagrant dest=/home/vagrant/examples owner=vagrant group=vagrant state=link 28 | 29 | # setup the jupyter notebook service 30 | - name: ensure that the .jupyter directory exists 31 | file: path=/home/vagrant/.jupyter state=directory 32 | - name: create 'notebooks' link in the home directory 33 | file: src=/vagrant/notebooks dest=/home/vagrant/notebooks owner=vagrant group=vagrant state=link 34 | - name: copy jupyter notebook configuration file 35 | copy: src=jupyter-config/jupyter_notebook_config.py dest=/home/vagrant/.jupyter/jupyter_notebook.config.py force=yes 36 | - name: link about notebook 37 | file: src=/vagrant/jupyter-config/about.ipynb dest=/home/vagrant/about.ipynb owner=vagrant group=vagrant state=link 38 | 39 | # add startup script to enable the import notebook function 40 | - name: verify that the profile directory exists 41 | sudo: true 42 | file: path=/home/vagrant/.ipython/profile_default/startup state=directory 43 | - name: copy notebook import as startup script 44 | copy: src=jupyter-config/10-nb-import-hook.py dest=/home/vagrant/.ipython/profile_default/startup/10-nb-import-hook.py force=yes 45 | 46 | # install jupyter notebook as a service 47 | - name: copy jupyter upstart script 48 | copy: src=jupyter-config/jupyter-notebook.upstart dest=/etc/init/jupyter-notebook.conf force=yes 49 | - name: start jupyter service 50 | service: name=jupyter-notebook state=restarted enabled=yes -------------------------------------------------------------------------------- /split_cli_output/split_cli_ouput.py: -------------------------------------------------------------------------------- 1 | import os 2 | import re 3 | 4 | 5 | def get_files_in_path(root_dir, only_ext="log"): 6 | """returns a list with all files from the given directory""" 7 | files = [os.path.join(dirpath, file) 8 | for (dirpath, dirnames, filenames) in os.walk(root_dir) 9 | for file in filenames] 10 | 11 | if only_ext: 12 | ends_with = only_ext if only_ext[0] == "." else "." + only_ext 13 | return [file for file in files if file.endswith(ends_with)] 14 | 15 | else: 16 | return files 17 | 18 | 19 | def split_config_file(raw_data): 20 | """splits multiple outputs from a single configuration file""" 21 | split_config = re.split("\n\S+#", raw_data) 22 | 23 | commands = dict() 24 | if len(split_config) > 0: 25 | # ignore the first element if it doesn't start with the prompt 26 | if not re.match("^\S+#", split_config[0], re.MULTILINE): 27 | split_config = split_config[1:] 28 | 29 | for single_command in split_config: 30 | lines = single_command.splitlines() 31 | 32 | # skip the command if no output is given 33 | if len(lines) > 1: 34 | # ensure that only the command is used as key 35 | if re.match("^\S+#", lines[0]): 36 | cmd = lines[0].split("#")[1] 37 | else: 38 | cmd = lines[0] 39 | 40 | commands[cmd] = "\n".join(lines[1:]) 41 | 42 | return commands 43 | 44 | 45 | if __name__ == "__main__": 46 | INPUT_DIRECTORY = "_input" 47 | OUTPUT_DIRECTORY = "_output" 48 | 49 | files = get_files_in_path(INPUT_DIRECTORY, only_ext="log") 50 | 51 | for file_path in files: 52 | if not os.path.isfile(file_path): 53 | print("File not found or no file: %s -- skip it" % file_path) 54 | 55 | else: 56 | with open(file_path) as f: 57 | raw_data = f.read() 58 | 59 | # the file name contains only the hostname following an extension 60 | hostname = os.path.basename(file_path)[:-len(".log")] 61 | 62 | # split the file in a : dictionary 63 | result = split_config_file(raw_data) 64 | 65 | # write results to the directory 66 | root_dir = os.path.join(OUTPUT_DIRECTORY, hostname) 67 | os.makedirs(root_dir, exist_ok=True) 68 | for command in result: 69 | with open(os.path.join(root_dir, "%s.txt" % command.strip()), "w+") as f: 70 | f.write(result[command]) 71 | --------------------------------------------------------------------------------