# 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)