Created
October 21, 2018 17:19
-
-
Save cidrblock/198ea7b516779fc71836aeb9cc5ab294 to your computer and use it in GitHub Desktop.
Fleet configuration management OO prototyping
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
""" fleet prototype | |
""" | |
import os | |
import multiprocessing.pool | |
from netmiko.ssh_autodetect import SSHDetect | |
from netmiko.ssh_dispatcher import ConnectHandler | |
class Configuration(): #pylint: disable=R0903 | |
""" A class for a configuration | |
""" | |
def __init__(self, current_raw): | |
self.current_raw = RawConfiguration(current_raw) | |
self.current_sliced = SlicedConfiguration(self.current_raw) | |
self.desired_sliced = SlicedConfiguration(self.current_raw) | |
@property | |
def changed(self): | |
""" Has the config changed | |
""" | |
return str(self.desired_sliced) != str(self.current_sliced) | |
def upsert(self, configuration_line): | |
""" Insert or update the desired configuration with a new | |
configuration_line | |
Args: | |
current_raw (ConfigurationLine): The new line | |
""" | |
match = [(idx, ds) for idx, ds in enumerate(self.desired_sliced.lines) | |
if ds.line == configuration_line.line] | |
if match: | |
self.desired_sliced.lines[match[0][0]] = configuration_line | |
result = "updated" | |
else: | |
self.desired_sliced.lines.append(configuration_line) | |
result = "added" | |
return result | |
class ConfigurationLine(): | |
""" A class for a configuraiton line | |
""" | |
def __init__(self, line): | |
self.line = line | |
self.children = [] | |
@property | |
def indent(self): | |
""" Return the indent of the current line | |
""" | |
return len(self.line) - len(self.line.lstrip()) | |
def __repr__(self): | |
output = [self.line] | |
output.extend([str(child) for child in self.children]) | |
return "\n".join(output) | |
class Fleet(): | |
""" A fleet class | |
""" | |
def __init__(self): | |
self.devices = [] | |
self.credentials = (os.environ['SSH_USERNAME'], os.environ['SSH_PASSWORD']) | |
def add(self, names): | |
""" Add devices to the fleet | |
Args: | |
names (list): A list of devices to add | |
""" | |
for name in names: | |
self.devices.append(ManagedDevice(name=name)) | |
def all(self, opersys, action, config): | |
""" Perform acctions on all devices | |
Args: | |
opersys (string): The os scope | |
action (string): The action to take | |
config (string): The new desired config line | |
""" | |
for device in self.devices: | |
if device.device_type == opersys: | |
getattr(device.configuration, action)(config) | |
@property | |
def changed(self): | |
""" Determine which devices have changed | |
""" | |
return [d.name for d in self.devices if d.configuration.changed] | |
def gather_facts(self): | |
""" Gather facts on all managed devices | |
""" | |
with multiprocessing.pool.ThreadPool(5) as pool: | |
results = [pool.apply_async(device.gather_facts, | |
[self.credentials]) | |
for device in self.devices] | |
for async_result in results: | |
async_result.get() | |
class ManagedDevice(): #pylint: disable=R0903 | |
""" A managed device class | |
""" | |
def __init__(self, name): | |
self.name = name | |
self.configuration = None | |
self.remote_device = None | |
self.device_type = None | |
def gather_facts(self, credentials): | |
""" Get the os and running config from a device | |
""" | |
remote_device = {'device_type': 'autodetect', | |
'host': self.name, | |
'username': credentials[0], | |
'password': credentials[1]} | |
guesser = SSHDetect(**remote_device) | |
best_match = guesser.autodetect() | |
self.device_type = best_match.split("_")[1] | |
remote_device['device_type'] = best_match | |
self.remote_device = remote_device | |
net_connect = ConnectHandler(**self.remote_device) | |
output = net_connect.send_command("show run") | |
self.configuration = Configuration(output) | |
class RawConfiguration(): #pylint: disable=R0903 | |
""" A class for a raw configuration | |
""" | |
def __init__(self, text): | |
self.text = text | |
self.lines = self.text.splitlines() | |
def __repr__(self): | |
return self.text | |
class SlicedConfiguration(): #pylint: disable=R0903 | |
""" A class for a sliced configuration | |
""" | |
def __init__(self, raw): | |
sliced = [] | |
for line in raw.lines: | |
line = ConfigurationLine(line) | |
if line.indent == 0: | |
sliced.append(line) | |
else: | |
sliced[-1].children.append(line) | |
self.lines = sliced | |
def __repr__(self): | |
output = [str(l) for l in self.lines] | |
return "\n".join(output) | |
def main(): | |
""" start here | |
""" | |
fleet = Fleet() | |
fleet.add(["router1", "router2", "router3"]) | |
fleet.gather_facts() | |
acl = ConfigurationLine("ip access-list foo") | |
acl.children.append(" 10 permit ip 8.8.7.0/24 8.8.8.0/24") | |
acl.children.append(" 20 permit ip any 8.8.8.8/32") | |
fleet.all(opersys="nxos", action="upsert", config=acl) | |
print(fleet.changed) | |
if __name__ == "__main__": | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment