diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 5bc272d..432522e 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -15,6 +15,6 @@ repos:
args: ["--profile", "black"]
name: isort (python)
- repo: https://github.com/asottile/pyupgrade
- rev: v2.21.2
+ rev: v2.22.0
hooks:
- id: pyupgrade
diff --git a/README.md b/README.md
index be3e8c2..122b1b6 100644
--- a/README.md
+++ b/README.md
@@ -76,40 +76,35 @@ $ pip install -r requirements.txt --user
path_to\subnetting> python main.py
```
-**macOS or Linux**
+**macOS or Unix**
```bash
$ python3 main.py
```
-You will be prompted to enter the name of the CSV file containing input subnets, the gateway IP address, a name for the Excel file to be created, and the name of the sheet within the Excel file. _(All inputs have default values)_.
+You will be prompted to enter the name of the CSV file containing input subnets, the gateway, a name for the Excel file to be created. _(All inputs have default values)_.
> A `subnets.csv` file can be found in the repo. This file is an entry point to get started using this program. It's prepopulated with three different subnets. _(Class A, B, and C)_.
```bash
-- CSV file w/ extension? [Defaults to subnets.csv]:
-- The gateway, first or last IP Address? [0/1] [Defaults to 0]:
-- Excel file w/o extension? [Defaults to IP-Schema]: Test-Schema
-- Worksheet name? [Defaults to IP Schema Worksheet]: Test Worksheet
+- CSV file [subnets.csv]:
+- The gateway, first or last IP Address [0/1] [0]:
+- Excel file to create [New-Schema.xlsx]:
```
-> - Abbreviations:
- **w/: With**
- **w/o: Without**
-
Voila :sparkles: You have an Excel file that includes all required data about each subnet.
```bash
-Please check Test-Schema_.xlsx in current working directory.
+Please check New-Schema_.xlsx in current working directory.
```
> **Default behaviors:**
-> 1. CIDR notation with no prefix length will be handled as /32.
- For example, if you enter `10.0.0.0` without a prefix length in the CSV file, the script will handle it like `10.0.0.0/32`.
+> 1. CIDR notation with no prefix length will be handled as /32.
- For example, if you enter `10.0.0.1` without a prefix length in the CSV file, the script will handle it like `10.0.0.1/32`.
> 2. The header line **`Subnets in CIDR Notation`** within the `subnets.csv` file is automatically skipped. So, there is no need to manually remove it.
-> 3. Gateway input accepts 0 or 1 **ONLY** [Defaults to 0]. 0 picks the first IP address of the subnet, while 1 picks the last IP address.
-
-> 4. Microsoft Excel does not allow worksheet name longer than 31 characters. Worksheet names longer than 31 chars will be truncated.
+> 3. The gateway input accepts 0 or 1 **ONLY** [Defaults to 0]. 0 picks the first IP address of the subnet, while 1 picks the last IP address.
---
@@ -120,10 +115,6 @@ Finally, if you have a L3 switch and you want to create [SVI interfaces](https:/
```bash
$ python parse_excel.py --file .xlsx
```
-**OR**
-```
-$ python parse_excel.py -f .xlsx
-```
This Python script will generate a configuration file that includes all VLANs and their SVI interfaces.
@@ -135,6 +126,7 @@ This Python script will generate a configuration file that includes all VLANs an
**Terminal**
![Python CLI](assets/subnetting-cli.png)
+_Elapsed time is about 9 seconds in here because a CIDR notation like 10.0.0.0/8 is a little bit extensive to process._
**CSV File (Input File)**
![CSV File](assets/subnets-csv.png)
@@ -142,7 +134,7 @@ This Python script will generate a configuration file that includes all VLANs an
**Excel File (Output File)**
![Excel Preview](assets/preview.png)
-**python parse_excel.py --file .xlsx**
+**python parse_excel.py -f .xlsx**
![SVI CLI](assets/svi.png)
**SVI Template**
diff --git a/assets/subnetting-cli.png b/assets/subnetting-cli.png
index 3fc88af..a5237d8 100644
Binary files a/assets/subnetting-cli.png and b/assets/subnetting-cli.png differ
diff --git a/export_subnets.py b/export_subnets.py
index 9269101..9328297 100644
--- a/export_subnets.py
+++ b/export_subnets.py
@@ -1,7 +1,7 @@
#!usr/bin/env python3
from datetime import date
-from typing import AnyStr, Dict, List
+from typing import AnyStr, Dict, List, Optional
from termcolor import colored, cprint
from xlsxwriter import Workbook
@@ -9,26 +9,30 @@
def export_subnets(
subnets: List[Dict],
- workbook_name: AnyStr = "IP-Schema",
- worksheet_name: AnyStr = "IP Schema Worksheet",
+ workbook_name: Optional[AnyStr] = "New-Schema.xlsx",
):
- """Export an Excel file of entered subnets
-
- Args:
- subnets (List[Dict]): Processed subnets
- workbook_name (AnyStr, optional): Name of the Excel file. Defaults to "IP-Schema".
- worksheet_name (AnyStr, optional): Name of the sheet within the Excel file. Defaults to "IP Schema Worksheet".
-
- Raises:
- SystemExit: TypeError
+ """Exports an Excel file of subnetting data
+
+ Parameters
+ ----------
+ subnets : List[Dict]
+ List of subnets went througth subnetting
+ workbook_name : Optional[AnyStr], optional
+ Name of Workbook to create, by default "New-Schema.xlsx"
+
+ Raises
+ ------
+ SystemExit
+ TypeError, KeyError
"""
- excel_fname = f"{workbook_name}_{date.today()}.xlsx"
+ wb_name, ext = workbook_name.split(".")
+ excel_fname = f"{wb_name}_{date.today()}.{ext}"
# Create an Excel file
with Workbook(filename=excel_fname) as workbook:
# Create a sheet within the Excel file
- worksheet = workbook.add_worksheet(name=worksheet_name)
+ worksheet = workbook.add_worksheet(name="Subnetting Results")
# Filters
worksheet.autofilter("A1:L1")
# Freeze top row and 2 most left columns
@@ -51,7 +55,7 @@ def export_subnets(
}
# Header line format
- header_line_frmt = workbook.add_format(
+ h_frmt = workbook.add_format(
properties={
"bold": True,
"border": True,
@@ -62,15 +66,11 @@ def export_subnets(
# Create a header line row
for cell, value in header_line.items():
- worksheet.write_string(cell, value, cell_format=header_line_frmt)
+ worksheet.write_string(cell, value, cell_format=h_frmt)
# Generic cell format
c_frmt = workbook.add_format(
- properties={
- "border": True,
- "align": "center",
- "valign": "vcenter",
- }
+ properties={"border": True, "align": "center", "valign": "vcenter"}
)
# Format cell containing number
@@ -106,8 +106,6 @@ def export_subnets(
# Jump to next row
row += 1
- except TypeError as e:
- raise SystemExit(colored(f"export_subnets.py: {e}", "red"))
- except KeyError as e:
- raise SystemExit(colored(f"export_subnets.py: {e}", "red"))
- cprint(f"\nPlease check {excel_fname} in the PWD.\n", "green")
+ except (TypeError, KeyError) as e:
+ raise SystemExit(colored(text=f"export_subnets.py: {e}", color="red"))
+ cprint(text=f"\nPlease check {excel_fname} in the PWD.\n", color="green")
diff --git a/main.py b/main.py
index 8a2f519..fa7e73b 100644
--- a/main.py
+++ b/main.py
@@ -1,9 +1,10 @@
#!usr/bin/env python3
+import time
from getpass import getuser
from colorama import init
-from termcolor import colored
+from termcolor import colored, cprint
from export_subnets import export_subnets
from read_subnets import read_subnets
@@ -15,59 +16,43 @@
def main():
try:
# CSV file
- input_subnets = (
- input(f"\n- CSV file w/ extension? [Defaults to subnets.csv]: ")
- or "subnets.csv"
- )
- if ".csv" not in input_subnets:
- raise SystemExit(
- colored("Sorry! The input file MUST include .csv extension", "red")
- )
+ input_csv = input(f"\n- CSV file [subnets.csv]: ") or "subnets.csv"
gateway = int(
- input("- The gateway, first or last IP Address? [0/1] [Defaults to 0]: ")
- or "0"
+ input("- The gateway, first or last IP Address [0/1] [0]: ") or "0"
)
if gateway not in (0, 1):
raise SystemExit(
- colored(
- "0 and 1 are the only allowed values! 0: First IP, 1: Last IP",
- "red",
- )
+ colored(text="0 and 1 are the only allowed values!", color="red")
)
# Excel file name
workbook_name = (
- input("- Excel file w/o extension? [Defaults to IP-Schema]: ")
- or "IP-Schema"
- )
- if workbook_name.endswith(".xlsx"):
- raise SystemExit(colored("Oops! Please remove the .xlsx extension", "red"))
- # Excel sheet name
- worksheet_name = (
- input("- Worksheet name? [Defaults to IP Schema Worksheet]: ")
- or "IP Schema Worksheet"
+ input("- Excel file to create [New-Schema.xlsx]: ") or "New-Schema.xlsx"
)
+ start = time.perf_counter()
+
# Read CSV file
- subnets = read_subnets(file_path=input_subnets)
+ subnets = read_subnets(file_path=input_csv)
# Do Subnetting
network_subnets = subnetting(input_subnets=subnets, gateway=gateway)
# Export subnetting results to an Excel file
- export_subnets(
- subnets=network_subnets,
- workbook_name=workbook_name,
- worksheet_name=worksheet_name[:31],
- )
+ export_subnets(subnets=network_subnets, workbook_name=workbook_name)
+
+ end = time.perf_counter()
+
+ delta = round(end - start, 2)
+ cprint(text=f"Finished in {delta} second(s)", on_color="on_blue")
except FileNotFoundError:
raise SystemExit(
- colored(f"main.py: {input_subnets} file does not exist!", "red")
+ colored(text=f"`{input_csv}` file does not exist!", color="red")
)
except KeyboardInterrupt:
- raise SystemExit(colored(f"\nProcess interrupted by {getuser()}", "yellow"))
-
- print("Done")
+ raise SystemExit(
+ colored(text=f"Process interrupted by {getuser()}", color="yellow")
+ )
if __name__ == "__main__":
diff --git a/parse_excel.py b/parse_excel.py
index ecd4c2d..1bbed04 100644
--- a/parse_excel.py
+++ b/parse_excel.py
@@ -1,6 +1,5 @@
#!usr/bin/env python3
-import os
from argparse import ArgumentParser
from colorama import init
@@ -32,14 +31,7 @@
args = parser.parse_args()
-if not args.file.endswith(".xlsx"):
- raise SystemExit(colored("\nInvalid input file. The file MUST be a .xlsx", "red"))
-if not os.path.isfile(args.file):
- raise SystemExit(colored(f"{args.file} does not exist!", "red"))
-
-svi_generator(args.file) # Execute the svi_generator
-
-cprint(
- f'\nCreated {args.file.replace(".xlsx", "")}-svi-template.txt successfully.',
- "green",
-)
+try:
+ svi_generator(excel_file=args.file)
+except (FileNotFoundError, PermissionError) as e:
+ raise SystemExit(colored(text=e, color="red"))
diff --git a/read_subnets.py b/read_subnets.py
index 15c8612..a1bece1 100644
--- a/read_subnets.py
+++ b/read_subnets.py
@@ -1,29 +1,25 @@
#!usr/bin/env python3
import csv
-from typing import AnyStr, List
+from typing import AnyStr, Dict, List
-def read_subnets(file_path: AnyStr = "subnets.csv") -> List[List]:
+def read_subnets(file_path: AnyStr = "subnets.csv") -> List[Dict[AnyStr, AnyStr]]:
"""Reads CSV subnets file
- Args:
- file_path (AnyStr, optional): Path to subnets CSV file. Defaults to "subnets.csv".
+ Parameters
+ ----------
+ file_path : AnyStr, optional
+ Name of a CSV file, by default "subnets.csv"
- Returns:
- List[List]: Subnets in CIDR notation representation
+ Returns
+ -------
+ List[Dict[AnyStr, AnyStr]]
+ Subnets in CIDR Notation
"""
- # Define an empty list to hold all subnets
- subnets = []
-
# Read subnets CSV file
with open(file=file_path, mode="r") as csvfile:
next(csvfile) # Skip header line
- csv_data = csv.reader(
- csvfile, delimiter="\n", dialect="excel", doublequote=True
- )
- for subnet in csv_data:
- subnets.append(subnet[0])
-
- return subnets
+ csv_data = csv.DictReader(f=csvfile, fieldnames={"cidr"})
+ return [cidr for cidr in csv_data]
diff --git a/subnetting.py b/subnetting.py
index 009ce25..db052fd 100644
--- a/subnetting.py
+++ b/subnetting.py
@@ -2,89 +2,71 @@
import ipaddress
from ipaddress import AddressValueError, NetmaskValueError
-from typing import Dict, List
+from typing import Any, AnyStr, Dict, List
from termcolor import colored
-def subnetting(input_subnets: List[List], gateway: int) -> List[Dict]:
- """Does subnetting on each value entered by the user
+def subnetting(input_subnets: List[Dict], gateway: int) -> List[Dict[AnyStr, Any]]:
+ """Does subnetting CIDR Notation
- Args:
- input_subnets (List[List]): Subnets from CSV file
- gateway (int): An interger that decides which IP address is the gateway
+ Parameters
+ ----------
+ input_subnets : List[Dict]
+ Input subnets in CSV file
+ gateway : int
+ The selected Gateway
- Raises:
- SystemExit: AddressValueError
- SystemExit: NetmaskValueError
- SystemExit: TypeError
- SystemExit: ValueError
- SystemExit: IndexError
+ Returns
+ -------
+ List[Dict[AnyStr, Any]]
+ Result of subnetting
- Returns:
- List[Dict]: Networks details
+ Raises
+ ------
+ SystemExit
+ AddressValueError, NetmaskValueError, ValueError, TypeError, IndexError
"""
-
try:
+ # Empty list to hold all subnetting values
results = []
# Loop over input_subnets
for subnet in input_subnets:
- cidr_notation = ipaddress.IPv4Network(subnet)
+ cidr = ipaddress.IPv4Network(subnet["cidr"])
# Find range of IP addresses
- hosts = list(cidr_notation.hosts())
- start_ip = hosts[0]
- end_ip = hosts[-1]
+ hosts = list(cidr.hosts())
+ start_ip, end_ip = hosts[0], hosts[-1]
# Evaluate wildcard mask from netmask
- subnet_mask = str(cidr_notation.netmask).split(".")
- wildcard_mask = []
- for octet in subnet_mask:
- octet_value = 255 - int(octet)
- wildcard_mask.append(octet_value)
-
+ subnet_mask = str(cidr.netmask).split(".")
+ wildcard_mask = [255 - int(octet) for octet in subnet_mask]
wildcard_mask = ".".join(map(str, wildcard_mask))
# Output
- # Define an empty dictionary to hold all values
- network_details = {}
-
- network_details["cidr"] = str(cidr_notation)
- network_details["net_addr"] = str(cidr_notation.network_address)
- network_details["prefix_len"] = str(cidr_notation.prefixlen)
- network_details["broadcast_addr"] = str(cidr_notation.broadcast_address)
- network_details["netmask"] = str(cidr_notation.netmask)
- network_details["wildcard"] = wildcard_mask
- network_details["num_hosts"] = len(hosts)
-
- if start_ip == end_ip:
- network_details["range"] = str(start_ip)
- else:
- network_details["range"] = f"{start_ip} → {end_ip}"
-
- if gateway: # if gateway == 1:
- network_details["gateway"] = str(end_ip)
- else:
- network_details["gateway"] = str(start_ip)
+ network_details = {
+ "cidr": str(cidr),
+ "net_addr": str(cidr.network_address),
+ "prefix_len": str(cidr.prefixlen),
+ "broadcast_addr": str(cidr.broadcast_address),
+ "netmask": str(cidr.netmask),
+ "wildcard": wildcard_mask,
+ "num_hosts": len(hosts),
+ "range": str(start_ip)
+ if start_ip == end_ip
+ else f"{start_ip} → {end_ip}",
+ "gateway": str(end_ip) if gateway else str(start_ip),
+ }
results.append(network_details)
return results
- except AddressValueError as e:
- raise SystemExit(colored(f"subnetting.py: {e}", "red"))
- except NetmaskValueError as e:
- raise SystemExit(
- colored(f"subnetting.py: {e}. Please check {subnet} prefix length!", "red")
- )
- except TypeError as e:
+ except (
+ AddressValueError,
+ NetmaskValueError,
+ ValueError,
+ TypeError,
+ IndexError,
+ ) as e:
raise SystemExit(colored(f"subnetting.py: {e}", "red"))
- except ValueError as e:
- raise SystemExit(colored(f"subnetting.py: {e}.", "red"))
- except IndexError as e:
- raise SystemExit(
- colored(
- f"subnetting.py:{e}. The input CSV file MUST contain at least one sunbet.",
- "red",
- )
- )
diff --git a/svi.j2 b/svi.j2
index b63da9c..96152c1 100644
--- a/svi.j2
+++ b/svi.j2
@@ -4,13 +4,13 @@ configure terminal
!
{% for vlan in vlans %}
vlan {{ vlan.id }}
- name {{ vlan.name|replace(" ","_")|upper }}
+ name {{ vlan.name|replace(" ","_")|upper|truncate(32, False, "") }}
exit
!
{% endfor %}
{% for vlan in vlans %}
interface vlan {{ vlan.id }}
- ip address {{ vlan.ipaddr }} {{ vlan.mask }}
+ ip address {{ vlan.ip }} {{ vlan.mask }}
description *** {{ vlan.name|upper }} ***
{% if vlan.helper_addr|string != "nan" %}
ip helper-address {{ vlan.helper_addr }}
diff --git a/svi_generator.py b/svi_generator.py
index 11912fe..a009245 100644
--- a/svi_generator.py
+++ b/svi_generator.py
@@ -6,14 +6,16 @@
import pandas as pd
from jinja2 import Environment, FileSystemLoader
-from numpy import nan
+from termcolor import cprint
def svi_generator(excel_file: AnyStr) -> None:
"""Generates an SVI configuration template
- Args:
- excel_file (AnyStr): Path to the Excel file
+ Parameters
+ ----------
+ excel_file : AnyStr
+ Name of an Excel file
"""
# Handle Jinja template
@@ -29,23 +31,27 @@ def svi_generator(excel_file: AnyStr) -> None:
data = pd.read_excel(
io=os.path.join("./", excel_file), sheet_name=0, usecols="A:B,H:J"
)
- df = pd.DataFrame(data=data)
-
- # Create a vlans List[Dict] from columns
- vlans = df.rename(
- columns={
- "VLAN ID": "id",
- "VLAN Name": "name",
- "Gateway": "ipaddr",
- "Subnet Mask": "mask",
- "IP Helper Address": "helper_addr",
- }
- ).to_dict(orient="records")
+
+ vlans = (
+ pd.DataFrame(data=data)
+ .rename(
+ columns={
+ "VLAN ID": "id",
+ "VLAN Name": "name",
+ "Gateway": "ip",
+ "Subnet Mask": "mask",
+ "IP Helper Address": "helper_addr",
+ }
+ )
+ .to_dict(orient="records")
+ )
# Render the templpate
svi_cfg = template.render(vlans=vlans)
# Export the template result to a text file
- cfg_fname = f'{excel_file.replace(".xlsx", "")}_svi_template.txt'
+ cfg_fname = f'{excel_file.replace(".xlsx", "")}_svi.txt'
with open(file=cfg_fname, mode="w", encoding="utf-8") as cfg_file:
- cfg_file.write(svi_cfg.lstrip())
+ cfg_file.write(svi_cfg)
+
+ cprint(text=f"\nCreated {cfg_fname} successfully.", color="green")