Yoinked from my Confluence proof-of-concept page.
This commit is contained in:
163
engine.py
Normal file
163
engine.py
Normal file
@@ -0,0 +1,163 @@
|
||||
# Config Generator
|
||||
# Written by Hex Ripley
|
||||
import re
|
||||
import ipaddress
|
||||
|
||||
#### Regex Templates ####
|
||||
|
||||
# Valid IP/CIDR notation pattern
|
||||
ip_cidr_pattern = re.compile(r'^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}|\[?[a-fA-F0-9:\.]+\]?)\/(\d{1,2})$') # Regex identifies valid ip/cidr input
|
||||
# use with ValueError | if ip_cidr_pattern.match(ip_cidr):
|
||||
# when building | ...
|
||||
# interpreters and | else:
|
||||
# evaluators | raise ValueError("Invalid IP/CIDR notation")
|
||||
|
||||
# Valid JUNOS interface pattern E.G. ae0 or xe-0/1/2
|
||||
interface_pattern = re.compile(r'^(ae[0-9]{1,2}|[gx]e-[0-9]\/[0-9]\/[0-9])$')
|
||||
|
||||
#### Input interpreters ####
|
||||
|
||||
def get_cidr(ip_cidr): # Grab the CIDR notation off a user's input
|
||||
if ip_cidr_pattern.match(ip_cidr):
|
||||
network = ipaddress.ip_network(ip_cidr, strict=False)
|
||||
prefix_length = network.prefixlen
|
||||
return prefix_length
|
||||
else:
|
||||
raise ValueError("Invalid ip/cidr notation on get_cidr call")
|
||||
|
||||
def get_network_block(ip_cidr): # If the user gives an address that is not the network address, find the network address.
|
||||
if ip_cidr_pattern.match(ip_cidr):
|
||||
network = ipaddress.ip_network(ip_cidr, strict=False)
|
||||
network_address = network.network_address
|
||||
prefix_length = network.prefixlen
|
||||
network_block = f"{network_address}/{prefix_length}"
|
||||
return network_block
|
||||
else:
|
||||
raise ValueError("Invalid ip/cidr notation on get_network_block call")
|
||||
|
||||
def get_next_ip_in_subnet(ip_cidr): # Return the next IP within the subnet
|
||||
if ip_cidr_pattern.match(ip_cidr):
|
||||
network = ipaddress.ip_network(ip_cidr, strict=False)
|
||||
ip = ipaddress.ip_address(ip_cidr.split('/')[0])
|
||||
if ip == network.broadcast_address:
|
||||
raise ValueError("IP address is the broadcast address; no next IP in the subnet.")
|
||||
next_ip = ip + 1
|
||||
if next_ip in network: # Make sure next IP is within subnet
|
||||
return f"{next_ip}/{network.prefixlen}"
|
||||
else:
|
||||
raise ValueError("Next IP address is outside the subnet.")
|
||||
|
||||
#### Evaluator Engine ####
|
||||
|
||||
def template_engine(config_data_template): # Does the work!
|
||||
data_strings = {}
|
||||
template_strings = config_data_template["template_output"]
|
||||
evaluation_functions = config_data_template["evaluation_functions"]
|
||||
interpreter_functions = config_data_template["interpreter_functions"]
|
||||
|
||||
for evaluator in evaluation_functions.items():
|
||||
template_string = evaluator[0]
|
||||
prompt_and_evaluator = evaluator[1]
|
||||
evaluated_input = get_valid_input(prompt_and_evaluator[0],prompt_and_evaluator[1])
|
||||
data_strings[template_string] = evaluated_input
|
||||
|
||||
for interpreter in interpreter_functions.items():
|
||||
template_string = interpreter[0]
|
||||
interpreter_and_key = interpreter[1]
|
||||
interpreted_value = interpreter_and_key[0](data_strings[interpreter_and_key[1]])
|
||||
data_strings[template_string] = interpreted_value
|
||||
|
||||
adjusted_strings = replace_target_words(template_strings,data_strings)
|
||||
print("\n ---- \n")
|
||||
for string in adjusted_strings:
|
||||
print(string)
|
||||
print("\n")
|
||||
|
||||
def replace_target_words(strings, replacements): # Function to replace words in a string
|
||||
def replace_in_string(s):
|
||||
# Find all target words in braces (e.g., {word})
|
||||
return re.sub(r'\{([a-zA-Z0-9_]+)\}', lambda match: replacements.get(match.group(1), match.group(1)), s)
|
||||
|
||||
# Apply replacement to all strings
|
||||
return [replace_in_string(s) for s in strings]
|
||||
|
||||
def get_valid_input(prompt, eval_func): # Input validater, does not proceed until validation function is satisfied.
|
||||
while True:
|
||||
user_input = input(prompt+" > ")
|
||||
try:
|
||||
# Attempt to evaluate the input using the provided function
|
||||
result = eval_func(user_input)
|
||||
return result
|
||||
except Exception as e:
|
||||
# Print error message and prompt again
|
||||
print(f"Invalid input: {e}. Please try again.")
|
||||
|
||||
#### Evaluator Functions ####
|
||||
|
||||
def eval_interface(interface): # Check if interface is valid.
|
||||
if interface_pattern.match(interface):
|
||||
return interface
|
||||
else:
|
||||
raise ValueError("Invalid Interface: Correct examples ae0, ge-1/2/4")
|
||||
|
||||
def eval_unit_number(unit_number): # Check if unit number is valid for what we expect to be configured.
|
||||
try:
|
||||
if unit_number is not None and 0 < int(unit_number) < 30000:
|
||||
return unit_number
|
||||
else:
|
||||
return ValueError()
|
||||
except:
|
||||
raise ValueError("Invalid Unit Number: Enter a unit greater than 0 and less than 30,000")
|
||||
|
||||
def eval_access_block(access_block):
|
||||
if ip_cidr_pattern.match(access_block):
|
||||
return access_block
|
||||
else:
|
||||
raise ValueError("Invalid ip/cidr notation")
|
||||
|
||||
def eval_transport_block(transport_block):
|
||||
if ip_cidr_pattern.match(transport_block):
|
||||
if get_cidr(transport_block) == 64:
|
||||
return transport_block
|
||||
else:
|
||||
raise ValueError("Transport Block CIDR must be 64")
|
||||
else:
|
||||
raise ValueError("Invalid ip/cidr notation")
|
||||
|
||||
#### Data Template ####
|
||||
|
||||
#### Data Templates are a dict with three keys:
|
||||
|
||||
## template_output ##
|
||||
|
||||
#template_output is a list of lines of strings containing lines of config with desired variables in braces.
|
||||
|
||||
## evaluation_functions ##
|
||||
|
||||
#evaluation_functions is a dict with the keys being the target variable and the values being a list containing the input for the get_valid_input function, which takes the first value as a prompt to show to the user and the second value as an evaluator function to run on the user's input.
|
||||
|
||||
## interpreter_functions ##
|
||||
|
||||
#interpreter_functions is a dict with keys being the target variable and the values being a list containing the desired interpreter function and the target value for the interpreter function. The target value for the interpreter function can be the key of a previous evaluation function or previous interpreter_function.
|
||||
|
||||
config_ipv6_junos = {
|
||||
"template_output" : [
|
||||
"set interfaces {interface} unit {unit_number} family inet6 address {access_first}",
|
||||
"set routing-options rib inet6.0 static route {transport_block} next-hop {access_second}",
|
||||
"set routing-options rib inet6.0 static route {transport_block} tag 101",
|
||||
"set policy-options prefix-list bgp-advertise-v6 {access_block}"
|
||||
] ,
|
||||
"evaluation_functions" : {
|
||||
"interface" : ["What is the user's interface?",eval_interface],
|
||||
"unit_number" : ["What is the user's unit number?",eval_unit_number],
|
||||
"access_block" : ["What is the IPv6 Access Block?",eval_access_block],
|
||||
"transport_block" : ["What is the IPv6 Transport Block?",eval_transport_block]
|
||||
} ,
|
||||
"interpreter_functions" : {
|
||||
"access_first" : [get_next_ip_in_subnet,"access_block"],
|
||||
"access_second" : [get_next_ip_in_subnet,"access_first"]
|
||||
} }
|
||||
|
||||
### Run this example
|
||||
|
||||
template_engine(config_ipv6_junos)
|
||||
Reference in New Issue
Block a user