commit 0578d398e624c69ba1f562d748be2e202f323e2f Author: hex Date: Thu Jul 10 19:42:22 2025 +0000 Yoinked from my Confluence proof-of-concept page. diff --git a/engine.py b/engine.py new file mode 100644 index 0000000..9ed0b6d --- /dev/null +++ b/engine.py @@ -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) \ No newline at end of file