Porting code over from Github
This commit is contained in:
parent
bf29bdae42
commit
5f0ea9976f
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
build/*
|
||||||
|
dist/*
|
||||||
|
ndct.egg-info/*
|
||||||
|
*/.DS_Store
|
93
README.md
93
README.md
@ -1,2 +1,93 @@
|
|||||||
# NDCT
|
# NDCT (Network Device Configuration Tool)
|
||||||
|
A configuration tool for efficient network device management.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
###### Generate network device configuration (Juniper Junos, Cisco IOS, Cisco Nexus, Cisco ASA)
|
||||||
|
- Multivendor device configuration generation from a single YAML file
|
||||||
|
|
||||||
|
###### Deployments
|
||||||
|
- Push configuration
|
||||||
|
- Pull configuration
|
||||||
|
- Get device information
|
||||||
|
|
||||||
|
###### Interface
|
||||||
|
- CLI
|
||||||
|
|
||||||
|
###### File encryption
|
||||||
|
- AES 128-bit
|
||||||
|
|
||||||
|
###### Full operation logging
|
||||||
|
|
||||||
|
# Libraries
|
||||||
|
>
|
||||||
|
|
||||||
|
# Installation
|
||||||
|
```
|
||||||
|
$ cd NDCT
|
||||||
|
$ python3 setup.py install
|
||||||
|
```
|
||||||
|
|
||||||
|
# Commands
|
||||||
|
```
|
||||||
|
$ ndct _____
|
||||||
|
|
||||||
|
configuration Configuration actions
|
||||||
|
crypt Crypt actions
|
||||||
|
deployment Deployment actions
|
||||||
|
device Device actions
|
||||||
|
```
|
||||||
|
Add single device
|
||||||
|
|
||||||
|
ndct device add -n DeviceName -i 192.168.1.1 -u username -p password -o cisco_ios|vyos
|
||||||
|
***
|
||||||
|
|
||||||
|
Add multiple devices from a file
|
||||||
|
|
||||||
|
ndct device add-from-file -f example.txt
|
||||||
|
***
|
||||||
|
|
||||||
|
Remove device
|
||||||
|
|
||||||
|
ndct device remove -n DeviceName
|
||||||
|
***
|
||||||
|
|
||||||
|
View device
|
||||||
|
|
||||||
|
ndct device view -n DeviceName
|
||||||
|
***
|
||||||
|
|
||||||
|
Add deployment
|
||||||
|
|
||||||
|
ndct deployment add -n DeploymentName -t Target1 Target2 -a get|deploy_generated|deploy_custom
|
||||||
|
***
|
||||||
|
|
||||||
|
Remove deployment
|
||||||
|
|
||||||
|
ndct deployment remove -n DeploymentName
|
||||||
|
***
|
||||||
|
|
||||||
|
View deployment
|
||||||
|
|
||||||
|
ndct deployment view -n DeploymentName
|
||||||
|
***
|
||||||
|
|
||||||
|
Run deployment
|
||||||
|
|
||||||
|
ndct deployment run -n DeploymentName
|
||||||
|
***
|
||||||
|
|
||||||
|
List stored configuration files
|
||||||
|
|
||||||
|
ndct configuration stored
|
||||||
|
***
|
||||||
|
|
||||||
|
Config diff
|
||||||
|
|
||||||
|
ndct configuration diff -c1 R1_generated.txt -c2 R2_generated.txt
|
||||||
|
***
|
||||||
|
|
||||||
|
Generate config
|
||||||
|
|
||||||
|
ndct configuration generate -n MyDevice
|
||||||
|
***
|
||||||
|
BIN
ndct/.___init__.py
Normal file
BIN
ndct/.___init__.py
Normal file
Binary file not shown.
BIN
ndct/._cli
Executable file
BIN
ndct/._cli
Executable file
Binary file not shown.
BIN
ndct/._core
Executable file
BIN
ndct/._core
Executable file
Binary file not shown.
BIN
ndct/._modules
Executable file
BIN
ndct/._modules
Executable file
Binary file not shown.
0
ndct/__init__.py
Normal file
0
ndct/__init__.py
Normal file
BIN
ndct/cli/.___init__.py
Normal file
BIN
ndct/cli/.___init__.py
Normal file
Binary file not shown.
BIN
ndct/cli/._configuration.py
Normal file
BIN
ndct/cli/._configuration.py
Normal file
Binary file not shown.
BIN
ndct/cli/._crypt.py
Normal file
BIN
ndct/cli/._crypt.py
Normal file
Binary file not shown.
BIN
ndct/cli/._deployment.py
Normal file
BIN
ndct/cli/._deployment.py
Normal file
Binary file not shown.
BIN
ndct/cli/._device.py
Normal file
BIN
ndct/cli/._device.py
Normal file
Binary file not shown.
BIN
ndct/cli/._main.py
Normal file
BIN
ndct/cli/._main.py
Normal file
Binary file not shown.
0
ndct/cli/__init__.py
Normal file
0
ndct/cli/__init__.py
Normal file
93
ndct/cli/configuration.py
Normal file
93
ndct/cli/configuration.py
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
import click
|
||||||
|
import os
|
||||||
|
import difflib
|
||||||
|
import yaml
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from jinja2 import Environment, FileSystemLoader
|
||||||
|
from ndct.core.device import Device
|
||||||
|
from ndct.core.log import log
|
||||||
|
from ndct.core.paths import METADATA_PATH, MODULE_PATH, CONFIG_PATH
|
||||||
|
|
||||||
|
@click.command(short_help = 'Generate a configuration')
|
||||||
|
@click.option('-n', '--name', help='Name', required = True)
|
||||||
|
def generate(name):
|
||||||
|
'''
|
||||||
|
Summary:
|
||||||
|
Generates a configuration file for a specified device.
|
||||||
|
|
||||||
|
Takes:
|
||||||
|
name: Device to generate configuration for
|
||||||
|
'''
|
||||||
|
Device.get_devices_from_file()
|
||||||
|
|
||||||
|
if os.path.isfile(METADATA_PATH + name + '_metadata.yaml'):
|
||||||
|
with open(METADATA_PATH + name + '_metadata.yaml', 'r') as metadata:
|
||||||
|
device_metadata = (yaml.safe_load(metadata))
|
||||||
|
else:
|
||||||
|
log('[{}] Metadata does not exist'.format(name), 'info')
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
device_os = Device.get_device_information(name)['os']
|
||||||
|
|
||||||
|
environment = MODULE_PATH + device_os + '/'
|
||||||
|
|
||||||
|
j2_env = Environment(loader=FileSystemLoader(environment), trim_blocks=True, lstrip_blocks=True)
|
||||||
|
|
||||||
|
for device, data in device_metadata.items():
|
||||||
|
configuration = j2_env.get_template('template.j2').render(data)
|
||||||
|
|
||||||
|
with open(CONFIG_PATH + name + '_generated.txt', 'w') as generated_config_file:
|
||||||
|
generated_config_file.write(configuration)
|
||||||
|
|
||||||
|
log('[{}] Generated configuration'.format(name), 'info')
|
||||||
|
|
||||||
|
@click.command(short_help = 'List configuration files')
|
||||||
|
def stored():
|
||||||
|
'''
|
||||||
|
Summary:
|
||||||
|
Lists all stored configuration files.
|
||||||
|
|
||||||
|
Takes:
|
||||||
|
none
|
||||||
|
'''
|
||||||
|
configurations = os.listdir(CONFIG_PATH)
|
||||||
|
|
||||||
|
if configurations:
|
||||||
|
log('Stored configuration files:', 'info')
|
||||||
|
for configuration_file in configurations:
|
||||||
|
if configuration_file != '__init__.py':
|
||||||
|
log(configuration_file, 'info')
|
||||||
|
else:
|
||||||
|
log('No configuration files stored', 'info')
|
||||||
|
|
||||||
|
@click.command(short_help = 'Show the difference between two configuration files')
|
||||||
|
@click.option('-c1', '--config1', help='Configuration file 1', required = True)
|
||||||
|
@click.option('-c2', '--config2', help='Configuration file 2', required = True)
|
||||||
|
def diff(config1, config2):
|
||||||
|
'''
|
||||||
|
Summary:
|
||||||
|
Outputs the difference between two device configuration files by comparing them line by line.
|
||||||
|
|
||||||
|
Takes:
|
||||||
|
config1: First configuration file
|
||||||
|
config2: Configuration file to compare with
|
||||||
|
'''
|
||||||
|
config1_lines = open(CONFIG_PATH + config1).read().splitlines()
|
||||||
|
config2_lines = open(CONFIG_PATH + config2).read().splitlines()
|
||||||
|
diff = difflib.unified_diff(config1_lines, config2_lines)
|
||||||
|
|
||||||
|
log('Diff for [{}] < > [{}]'.format(config1, config2), 'info')
|
||||||
|
for line in diff:
|
||||||
|
if line[0] == '+' and line[1] != '+':
|
||||||
|
log('\033[0;32m{}\033[m'.format(line), 'info')
|
||||||
|
elif line[0] == '-' and line[1] != '-':
|
||||||
|
log('\033[0;31m{}\033[m'.format(line), 'info')
|
||||||
|
|
||||||
|
@click.group(short_help = 'Configuration commands')
|
||||||
|
def configuration():
|
||||||
|
pass
|
||||||
|
|
||||||
|
configuration.add_command(generate)
|
||||||
|
configuration.add_command(stored)
|
||||||
|
configuration.add_command(diff)
|
37
ndct/cli/crypt.py
Normal file
37
ndct/cli/crypt.py
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
import click
|
||||||
|
import json
|
||||||
|
|
||||||
|
from ast import literal_eval
|
||||||
|
from cryptography.fernet import Fernet
|
||||||
|
from ndct.core.crypt import Crypt
|
||||||
|
from ndct.core.log import log
|
||||||
|
from ndct.core.paths import DB_PATH
|
||||||
|
|
||||||
|
@click.command(short_help = 'Decrypt a file')
|
||||||
|
@click.option('-f', '--filename', type = click.Choice(['devices', 'deployments']), help = 'Filename', required = True)
|
||||||
|
def decrypt(filename):
|
||||||
|
'''
|
||||||
|
Summary:
|
||||||
|
Create a decrypted copy of a file in JSON format.
|
||||||
|
|
||||||
|
Takes:
|
||||||
|
filename: Name of decrypted file
|
||||||
|
'''
|
||||||
|
key = Crypt.get_key()
|
||||||
|
|
||||||
|
with open(DB_PATH + filename, 'rb') as encrypted_file:
|
||||||
|
data = encrypted_file.read()
|
||||||
|
|
||||||
|
fernet = Fernet(key)
|
||||||
|
decrypted_data = literal_eval(fernet.decrypt(data).decode())
|
||||||
|
|
||||||
|
with open(DB_PATH + filename + '.decrypted', 'w') as decrypted_file:
|
||||||
|
json.dump(decrypted_data, decrypted_file, indent=4)
|
||||||
|
|
||||||
|
log('Generated decrypted file {}.decrypted'.format(filename), 'info')
|
||||||
|
|
||||||
|
@click.group(short_help = 'Crypt commands')
|
||||||
|
def crypt():
|
||||||
|
pass
|
||||||
|
|
||||||
|
crypt.add_command(decrypt)
|
102
ndct/cli/deployment.py
Normal file
102
ndct/cli/deployment.py
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
import click
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from ndct.core.deployment import Deployment, deployments
|
||||||
|
from ndct.core.device import Device, devices
|
||||||
|
from ndct.core.log import log
|
||||||
|
|
||||||
|
@click.command(short_help = 'Add a deployment')
|
||||||
|
@click.option('-n', '--name', help = 'Name', required = True)
|
||||||
|
@click.option('-t', '--targets', nargs = 0, help = 'Devices to deploy to', required = True)
|
||||||
|
@click.option('-a', '--action', type = click.Choice(['get', 'deploy_generated', 'deploy_custom']), help = 'Deployment action', required = True)
|
||||||
|
@click.argument('targets', nargs = -1)
|
||||||
|
def add(name, targets, action):
|
||||||
|
'''
|
||||||
|
Summary:
|
||||||
|
Adds a deployment.
|
||||||
|
|
||||||
|
Takes:
|
||||||
|
name: Name of deployment
|
||||||
|
targets: Devices to target with the deployment
|
||||||
|
action: Action to perform get|deploy_generated|deploy_custom
|
||||||
|
'''
|
||||||
|
Deployment.get_deployments_from_file()
|
||||||
|
for deployment in deployments:
|
||||||
|
if name in deployment:
|
||||||
|
log('[{}] Deployment already exists'.format(name), 'info')
|
||||||
|
sys.exit(1)
|
||||||
|
deployment_object = Deployment(name, list(targets), action)
|
||||||
|
deployments.append({name: deployment_object})
|
||||||
|
log('[{}] Deployment added successfully with ID {}'.format(name, deployment_object.deployment_id), 'info')
|
||||||
|
Deployment.save_deployments_to_file()
|
||||||
|
|
||||||
|
@click.command(short_help = 'Remove a deployment')
|
||||||
|
@click.option('-n', '--name', help = 'Name', required = True)
|
||||||
|
def remove(name):
|
||||||
|
'''
|
||||||
|
Summary:
|
||||||
|
Removes a deployment.
|
||||||
|
|
||||||
|
Takes:
|
||||||
|
name: Name of deployment to remove
|
||||||
|
'''
|
||||||
|
Deployment.get_deployments_from_file()
|
||||||
|
for deployment in deployments:
|
||||||
|
if name in deployment:
|
||||||
|
deployments.remove(deployment)
|
||||||
|
log('[{}] Deployment removed successfully'.format(name), 'info')
|
||||||
|
Deployment.save_deployments_to_file()
|
||||||
|
return
|
||||||
|
|
||||||
|
log('[{}] Deployment does not exist'.format(name), 'error')
|
||||||
|
|
||||||
|
@click.command(short_help = 'View a deployment')
|
||||||
|
@click.option('-n', '--name', help = 'Name', required = True)
|
||||||
|
def view(name):
|
||||||
|
'''
|
||||||
|
Summary:
|
||||||
|
Prints attributes of a Deployment instance.
|
||||||
|
|
||||||
|
Takes:
|
||||||
|
name: Name of deployment to view information about
|
||||||
|
'''
|
||||||
|
Deployment.get_deployments_from_file()
|
||||||
|
for deployment in deployments:
|
||||||
|
if name in deployment:
|
||||||
|
deployment_dict = deployment[name].all()
|
||||||
|
log('Name: ' + str(deployment_dict['name']), 'info')
|
||||||
|
log('Targets: ' + str(deployment_dict['targets']), 'info')
|
||||||
|
log('Action: ' + str(deployment_dict['action']), 'info')
|
||||||
|
log('ID: ' + str(deployment_dict['deployment_id']), 'info')
|
||||||
|
log('Status: ' + str(deployment_dict['status']), 'info')
|
||||||
|
return
|
||||||
|
|
||||||
|
log('[{}] Deployment does not exist'.format(name), 'error')
|
||||||
|
|
||||||
|
@click.command(short_help = 'Run a deployment')
|
||||||
|
@click.option('-n', '--name', help = 'Name', required = True)
|
||||||
|
def run(name):
|
||||||
|
'''
|
||||||
|
Summary:
|
||||||
|
Calls the run method on a Deployment object.
|
||||||
|
|
||||||
|
Takes:
|
||||||
|
name: Name of deployment to run
|
||||||
|
'''
|
||||||
|
Device.get_devices_from_file()
|
||||||
|
Deployment.get_deployments_from_file()
|
||||||
|
for deployment in deployments:
|
||||||
|
if name in deployment:
|
||||||
|
deployment[name].run()
|
||||||
|
Deployment.save_deployments_to_file()
|
||||||
|
return
|
||||||
|
log('[{}] Deployment does not exist'.format(name), 'error')
|
||||||
|
|
||||||
|
@click.group(short_help = 'Deployment commands')
|
||||||
|
def deployment():
|
||||||
|
pass
|
||||||
|
|
||||||
|
deployment.add_command(add)
|
||||||
|
deployment.add_command(remove)
|
||||||
|
deployment.add_command(view)
|
||||||
|
deployment.add_command(run)
|
106
ndct/cli/device.py
Normal file
106
ndct/cli/device.py
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
import click
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
|
||||||
|
from ndct.core.device import Device, devices
|
||||||
|
from ndct.core.log import log
|
||||||
|
from ndct.core.paths import DB_PATH
|
||||||
|
|
||||||
|
@click.command(short_help = 'Add a device')
|
||||||
|
@click.option('-n', '--name', help = 'Name', required = True)
|
||||||
|
@click.option('-i', '--ip', help = 'IP address', required = True)
|
||||||
|
@click.option('-u', '--username', help = 'Username to authenticate against', required = True)
|
||||||
|
@click.option('-p', '--password', help = 'Password to authenticate with', required = True)
|
||||||
|
@click.option('-o', '--os', type = click.Choice(['cisco_ios', 'vyos']), help = 'Operating system', required = True)
|
||||||
|
def add(name, ip, username, password, os):
|
||||||
|
'''
|
||||||
|
Summary:
|
||||||
|
Adds a device.
|
||||||
|
|
||||||
|
Takes:
|
||||||
|
name: Name of device
|
||||||
|
ip: Management IP address of device
|
||||||
|
username: Username to authenticate against
|
||||||
|
password: Password to authenticate with
|
||||||
|
os: Operating system of device cisco_ios|vyos
|
||||||
|
'''
|
||||||
|
Device.get_devices_from_file()
|
||||||
|
for device in devices:
|
||||||
|
if name in device:
|
||||||
|
log('[{}] Device already exists'.format(name), 'info')
|
||||||
|
sys.exit(1)
|
||||||
|
device_object = Device(name, ip, username, password, os)
|
||||||
|
devices.append({name: device_object})
|
||||||
|
log('[{}] Device added successfully'.format(name), 'info')
|
||||||
|
Device.save_devices_to_file()
|
||||||
|
|
||||||
|
@click.command(short_help = 'Add devices from a file')
|
||||||
|
@click.option('-f', '--filename', help = 'File to add devices from', required = True)
|
||||||
|
def add_from_file(filename):
|
||||||
|
Device.get_devices_from_file()
|
||||||
|
file_path = DB_PATH + filename
|
||||||
|
if os.path.isfile(file_path):
|
||||||
|
log("Adding devices from '{}'".format(file_path), 'info')
|
||||||
|
with open(file_path, 'r') as devices_file:
|
||||||
|
all_lines = [line.strip() for line in devices_file.readlines()]
|
||||||
|
for device_attribute in range(0, len(all_lines), 5):
|
||||||
|
device_exists = False
|
||||||
|
for device in devices:
|
||||||
|
if all_lines[device_attribute] in device:
|
||||||
|
log('[{}] Device already exists'.format(all_lines[device_attribute]), 'info')
|
||||||
|
device_exists = True
|
||||||
|
if device_exists == False:
|
||||||
|
device_object = Device(all_lines[device_attribute], all_lines[device_attribute+1], all_lines[device_attribute+2], all_lines[device_attribute+3], all_lines[device_attribute+4])
|
||||||
|
devices.append({all_lines[device_attribute]: device_object})
|
||||||
|
log('[{}] Device added successfully'.format(all_lines[device_attribute]), 'info')
|
||||||
|
Device.save_devices_to_file()
|
||||||
|
|
||||||
|
@click.command(short_help = 'Remove a device')
|
||||||
|
@click.option('-n', '--name', help = 'Name', required = True)
|
||||||
|
def remove(name):
|
||||||
|
'''
|
||||||
|
Summary:
|
||||||
|
Removes a device.
|
||||||
|
|
||||||
|
Takes:
|
||||||
|
name: Name of device to remove
|
||||||
|
'''
|
||||||
|
Device.get_devices_from_file()
|
||||||
|
for device in devices:
|
||||||
|
if name in device:
|
||||||
|
devices.remove(device)
|
||||||
|
log('[{}] Device removed successfully'.format(name), 'info')
|
||||||
|
Device.save_devices_to_file()
|
||||||
|
return
|
||||||
|
|
||||||
|
log('[{}] Device does not exist'.format(name), 'error')
|
||||||
|
|
||||||
|
@click.command(short_help = 'View a device')
|
||||||
|
@click.option('-n', '--name', help = 'Name', required = True)
|
||||||
|
def view(name):
|
||||||
|
'''
|
||||||
|
Summary:
|
||||||
|
Prints attributes of a Device instance.
|
||||||
|
|
||||||
|
Takes:
|
||||||
|
name: Name of device to view information about
|
||||||
|
'''
|
||||||
|
Device.get_devices_from_file()
|
||||||
|
device_information = Device.get_device_information(name)
|
||||||
|
if device_information != None:
|
||||||
|
log('Name: ' + str(device_information['name']), 'info')
|
||||||
|
log('IP: ' + str(device_information['ip']), 'info')
|
||||||
|
log('Username: ' + str(device_information['username']), 'info')
|
||||||
|
log('Password: ' + str(device_information['password']), 'info')
|
||||||
|
log('OS: ' + str(device_information['os']), 'info')
|
||||||
|
else:
|
||||||
|
log('[{}] Device does not exist'.format(name), 'error')
|
||||||
|
|
||||||
|
@click.group(short_help = 'Device commands')
|
||||||
|
def device():
|
||||||
|
pass
|
||||||
|
|
||||||
|
device.add_command(add)
|
||||||
|
device.add_command(add_from_file)
|
||||||
|
device.add_command(remove)
|
||||||
|
device.add_command(view)
|
20
ndct/cli/main.py
Normal file
20
ndct/cli/main.py
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import click
|
||||||
|
|
||||||
|
from ndct.cli import crypt
|
||||||
|
from ndct.cli import device
|
||||||
|
from ndct.cli import deployment
|
||||||
|
from ndct.cli import configuration
|
||||||
|
from ndct.core.banner import banner
|
||||||
|
|
||||||
|
@click.group()
|
||||||
|
def main():
|
||||||
|
'''
|
||||||
|
Summary:
|
||||||
|
Network Device Configuration Tool.
|
||||||
|
'''
|
||||||
|
banner()
|
||||||
|
|
||||||
|
main.add_command(crypt.crypt, name = 'crypt')
|
||||||
|
main.add_command(device.device, name = 'device')
|
||||||
|
main.add_command(deployment.deployment, name = 'deployment')
|
||||||
|
main.add_command(configuration.configuration, name = 'configuration')
|
BIN
ndct/core/.___init__.py
Normal file
BIN
ndct/core/.___init__.py
Normal file
Binary file not shown.
BIN
ndct/core/._banner.py
Normal file
BIN
ndct/core/._banner.py
Normal file
Binary file not shown.
BIN
ndct/core/._configuration.py
Normal file
BIN
ndct/core/._configuration.py
Normal file
Binary file not shown.
BIN
ndct/core/._configuration_files
Executable file
BIN
ndct/core/._configuration_files
Executable file
Binary file not shown.
BIN
ndct/core/._connection.py
Normal file
BIN
ndct/core/._connection.py
Normal file
Binary file not shown.
BIN
ndct/core/._crypt.py
Normal file
BIN
ndct/core/._crypt.py
Normal file
Binary file not shown.
BIN
ndct/core/._db
Executable file
BIN
ndct/core/._db
Executable file
Binary file not shown.
BIN
ndct/core/._deployment.py
Normal file
BIN
ndct/core/._deployment.py
Normal file
Binary file not shown.
BIN
ndct/core/._device.py
Normal file
BIN
ndct/core/._device.py
Normal file
Binary file not shown.
BIN
ndct/core/._device_metadata
Executable file
BIN
ndct/core/._device_metadata
Executable file
Binary file not shown.
BIN
ndct/core/._log.py
Normal file
BIN
ndct/core/._log.py
Normal file
Binary file not shown.
BIN
ndct/core/._logs
Executable file
BIN
ndct/core/._logs
Executable file
Binary file not shown.
BIN
ndct/core/._paths.py
Normal file
BIN
ndct/core/._paths.py
Normal file
Binary file not shown.
0
ndct/core/__init__.py
Normal file
0
ndct/core/__init__.py
Normal file
12
ndct/core/banner.py
Normal file
12
ndct/core/banner.py
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
def banner():
|
||||||
|
print('''
|
||||||
|
Created by: github.com/m4cfarlane
|
||||||
|
_ _ ____ ____ _____
|
||||||
|
| \ | | _ \ / ___|_ _|
|
||||||
|
| \| | | | | | | |
|
||||||
|
| |\ | |_| | |___ | |
|
||||||
|
|_| \_|____/ \____| |_|
|
||||||
|
|
||||||
|
Network Device Configuration Tool
|
||||||
|
A configuration tool for efficient network device management.
|
||||||
|
''')
|
328
ndct/core/configuration.py
Normal file
328
ndct/core/configuration.py
Normal file
@ -0,0 +1,328 @@
|
|||||||
|
import json
|
||||||
|
import os
|
||||||
|
|
||||||
|
from datetime import datetime
|
||||||
|
from ndct.core.connection import Connection
|
||||||
|
from ndct.core.device import Device
|
||||||
|
from ndct.core.log import log
|
||||||
|
from ndct.core.paths import MODULE_PATH, CONFIG_PATH
|
||||||
|
|
||||||
|
class Configuration:
|
||||||
|
@staticmethod
|
||||||
|
def deploy_custom_configuration(device):
|
||||||
|
'''
|
||||||
|
Summary:
|
||||||
|
Deploys custom configuration from a file to a device.
|
||||||
|
|
||||||
|
Takes:
|
||||||
|
device: Device to deploy configuration to
|
||||||
|
'''
|
||||||
|
rolled_back = False
|
||||||
|
device_information = Device.get_device_information(device)
|
||||||
|
|
||||||
|
connection_object = Connection(
|
||||||
|
device_information['name'],
|
||||||
|
device_information['ip'],
|
||||||
|
device_information['username'],
|
||||||
|
device_information['password'],
|
||||||
|
device_information['os']
|
||||||
|
)
|
||||||
|
device_connection = connection_object.get_connection()
|
||||||
|
|
||||||
|
try:
|
||||||
|
Configuration.snapshot_configuration(device, device_connection, device_information['os'])
|
||||||
|
|
||||||
|
with open(CONFIG_PATH + device + '_custom_commands.txt') as custom_commands_from_file:
|
||||||
|
command_list = custom_commands_from_file.read().splitlines()
|
||||||
|
|
||||||
|
log('[{}] Pushing configuration...'.format(device), 'info')
|
||||||
|
device_connection.send_config_set(command_list)
|
||||||
|
|
||||||
|
Configuration.save_configuration(device_information['os'], device_connection)
|
||||||
|
|
||||||
|
for command in command_list:
|
||||||
|
if command != 'no shutdown' and rolled_back == False:
|
||||||
|
rolled_back = Configuration.check_configuration_line(
|
||||||
|
device,
|
||||||
|
device_connection,
|
||||||
|
device_information['os'],
|
||||||
|
command
|
||||||
|
)
|
||||||
|
|
||||||
|
connection_object.close_connection(device_connection)
|
||||||
|
|
||||||
|
Configuration.delete_rollback_configuration(device)
|
||||||
|
except AttributeError:
|
||||||
|
log('[{}] Could not send commands'.format(device), 'error')
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def deploy_generated_configuration(device):
|
||||||
|
'''
|
||||||
|
Summary:
|
||||||
|
Deploys configuration generated from device metadata to a device.
|
||||||
|
|
||||||
|
Takes:
|
||||||
|
device: Device to deploy configuration to
|
||||||
|
'''
|
||||||
|
device_information = Device.get_device_information(device)
|
||||||
|
|
||||||
|
connection_object = Connection(
|
||||||
|
device_information['name'],
|
||||||
|
device_information['ip'],
|
||||||
|
device_information['username'],
|
||||||
|
device_information['password'],
|
||||||
|
device_information['os']
|
||||||
|
)
|
||||||
|
device_connection = connection_object.get_connection()
|
||||||
|
|
||||||
|
try:
|
||||||
|
Configuration.snapshot_configuration(device, device_connection, device_information['os'])
|
||||||
|
|
||||||
|
log('[{}] Pushing configuration...'.format(device), 'info')
|
||||||
|
device_connection.send_config_from_file(CONFIG_PATH + device + '_generated.txt')
|
||||||
|
|
||||||
|
Configuration.save_configuration(device_information['os'], device_connection)
|
||||||
|
|
||||||
|
pushed_successfully = Configuration.check_full_configuration(
|
||||||
|
device,
|
||||||
|
device_connection,
|
||||||
|
device_information['os']
|
||||||
|
)
|
||||||
|
|
||||||
|
if pushed_successfully == True:
|
||||||
|
Configuration.mark_configuration_as_deployed(device)
|
||||||
|
Configuration.delete_rollback_configuration(device)
|
||||||
|
|
||||||
|
connection_object.close_connection(device_connection)
|
||||||
|
except AttributeError:
|
||||||
|
log('[{}] Could not send commands, device unreachable'.format(device), 'error')
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_configuration(device):
|
||||||
|
'''
|
||||||
|
Summary:
|
||||||
|
Gets current configuration from a device and stores it in a file.
|
||||||
|
|
||||||
|
Takes:
|
||||||
|
device: Device to get configuration from
|
||||||
|
'''
|
||||||
|
device_information = Device.get_device_information(device)
|
||||||
|
|
||||||
|
connection_object = Connection(
|
||||||
|
device_information['name'],
|
||||||
|
device_information['ip'],
|
||||||
|
device_information['username'],
|
||||||
|
device_information['password'],
|
||||||
|
device_information['os']
|
||||||
|
)
|
||||||
|
device_connection = connection_object.get_connection()
|
||||||
|
|
||||||
|
try:
|
||||||
|
with open(MODULE_PATH + device_information['os'] + '/commands.json') as command_list_from_file:
|
||||||
|
command_list = json.load(command_list_from_file)
|
||||||
|
|
||||||
|
log('[{}] Getting device configuration...'.format(device), 'info')
|
||||||
|
|
||||||
|
output = device_connection.send_command(command_list['commands']['config'])
|
||||||
|
|
||||||
|
configuration_lines = output.splitlines()
|
||||||
|
|
||||||
|
with open(CONFIG_PATH + device + '_latest.txt', 'w+') as configuration_file:
|
||||||
|
for line in configuration_lines:
|
||||||
|
configuration_file.write(line + '\n')
|
||||||
|
|
||||||
|
log('[{}] Device configuration stored as {}_latest.txt in {}'.format(device, device, CONFIG_PATH), 'info')
|
||||||
|
|
||||||
|
connection_object.close_connection(device_connection)
|
||||||
|
except AttributeError:
|
||||||
|
log('[{}] Could not send commands, device unreachable'.format(device), 'error')
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def check_configuration_line(device, device_connection, os, configuration_line):
|
||||||
|
'''
|
||||||
|
Summary:
|
||||||
|
Checks if configuration line has been pushed to device.
|
||||||
|
|
||||||
|
Takes:
|
||||||
|
device: Device name
|
||||||
|
device_connection: Device connection object
|
||||||
|
os: Operating system of device
|
||||||
|
configuration_line: Configuration line to check for
|
||||||
|
'''
|
||||||
|
with open(MODULE_PATH + os + '/commands.json') as command_file_temp:
|
||||||
|
command_file = json.load(command_file_temp)
|
||||||
|
|
||||||
|
configuration = device_connection.send_command(command_file['commands']['config'])
|
||||||
|
|
||||||
|
if configuration_line in configuration:
|
||||||
|
log("[{}] Configuration check passed for '{}'".format(device, configuration_line), 'info')
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
log("[{}] Configuration check failed for '{}', rolling back".format(device, configuration_line), 'info')
|
||||||
|
Configuration.rollback_configuration(device, os, device_connection)
|
||||||
|
return True
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def check_full_configuration(device, device_connection, os):
|
||||||
|
'''
|
||||||
|
Summary:
|
||||||
|
Checks if full configuration has been pushed to device.
|
||||||
|
|
||||||
|
Takes:
|
||||||
|
device: Device name
|
||||||
|
device_connection: Device connection object
|
||||||
|
os: Operating system of device
|
||||||
|
'''
|
||||||
|
full_configuration_pushed = True
|
||||||
|
do_not_check = ['!', ' no shutdown']
|
||||||
|
|
||||||
|
with open(MODULE_PATH + os + '/commands.json') as command_file_temp:
|
||||||
|
command_file = json.load(command_file_temp)
|
||||||
|
|
||||||
|
device_configuration = device_connection.send_command(command_file['commands']['config'])
|
||||||
|
|
||||||
|
with open(CONFIG_PATH + device + '_generated.txt') as pushed_configuration_temp:
|
||||||
|
pushed_configuration = pushed_configuration_temp.read().splitlines()
|
||||||
|
|
||||||
|
log('[{}] Checking configuration...'.format(device), 'info')
|
||||||
|
|
||||||
|
for configuration_line in pushed_configuration:
|
||||||
|
if configuration_line not in device_configuration and configuration_line not in do_not_check:
|
||||||
|
full_configuration_pushed = False
|
||||||
|
|
||||||
|
if full_configuration_pushed == True:
|
||||||
|
log('[{}] Configuration check was successful'.format(device), 'info')
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
log('[{}] Configuration check failed, check configuration manually'.format(device), 'error')
|
||||||
|
return False
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def save_configuration(os, device_connection):
|
||||||
|
'''
|
||||||
|
Summary:
|
||||||
|
Saves device configuration persistently.
|
||||||
|
|
||||||
|
Takes:
|
||||||
|
os: Operating system of device
|
||||||
|
device_connection: device_connection: Device connection object
|
||||||
|
'''
|
||||||
|
with open(MODULE_PATH + os + '/commands.json') as command_file_temp:
|
||||||
|
command_file = json.load(command_file_temp)
|
||||||
|
|
||||||
|
save_command = command_file['commands']['save_config']
|
||||||
|
|
||||||
|
if os == 'vyos':
|
||||||
|
device_connection.send_config_set(save_command)
|
||||||
|
elif os == 'cisco_ios':
|
||||||
|
output = device_connection.send_command_timing(save_command)
|
||||||
|
if 'Destination filename' in output:
|
||||||
|
device_connection.send_command_timing(
|
||||||
|
"\n", strip_prompt=False, strip_command=False
|
||||||
|
)
|
||||||
|
if 'Overwrite the previous' in output:
|
||||||
|
device_connection.send_command_timing(
|
||||||
|
"\n", strip_prompt=False, strip_command=False
|
||||||
|
)
|
||||||
|
if 'Warning: Attempting to overwrite an NVRAM configuration previously written' in output:
|
||||||
|
device_connection.send_command_timing(
|
||||||
|
"\n", strip_prompt=False, strip_command=False
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def mark_configuration_as_deployed(device):
|
||||||
|
'''
|
||||||
|
Summary:
|
||||||
|
Marks a generated configuration file as deployed.
|
||||||
|
|
||||||
|
Takes:
|
||||||
|
device: Name of device to mark configuration as deployed for
|
||||||
|
'''
|
||||||
|
with open(CONFIG_PATH + device + '_generated.txt') as generated_configuration_file:
|
||||||
|
deployed_configuration = generated_configuration_file.read()
|
||||||
|
|
||||||
|
with open(CONFIG_PATH + device + '_deployed_' + datetime.now().strftime('%Y-%m-%d_%H:%M:%S') + '.txt', 'w') as deployed_configuration_file:
|
||||||
|
deployed_configuration_file.write(deployed_configuration)
|
||||||
|
|
||||||
|
log('[{}] Marked generated configuration as deployed'.format(device), 'info')
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def snapshot_configuration(device, device_connection, os):
|
||||||
|
'''
|
||||||
|
Summary:
|
||||||
|
Takes a snapshot of device configuration for rollback configuration.
|
||||||
|
|
||||||
|
Takes:
|
||||||
|
device: Device name
|
||||||
|
'''
|
||||||
|
try:
|
||||||
|
with open(MODULE_PATH + os + '/commands.json') as command_list_from_file:
|
||||||
|
command_list = json.load(command_list_from_file)
|
||||||
|
|
||||||
|
log('[{}] Creating configuration snapshot...'.format(device), 'info')
|
||||||
|
|
||||||
|
output = device_connection.send_command(command_list['commands']['config'])
|
||||||
|
|
||||||
|
configuration_lines = output.splitlines()
|
||||||
|
|
||||||
|
with open(CONFIG_PATH + device + '_rollback.txt', 'w+') as configuration_file:
|
||||||
|
for line in configuration_lines:
|
||||||
|
configuration_file.write(line + '\n')
|
||||||
|
|
||||||
|
log('[{}] Configuration snapshot stored as {}_rollback.txt in {}'.format(device, device, CONFIG_PATH), 'info')
|
||||||
|
except AttributeError:
|
||||||
|
log('[{}] Could not send commands, device unreachable'.format(device), 'error')
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def rollback_configuration(device, os, device_connection):
|
||||||
|
'''
|
||||||
|
Summary:
|
||||||
|
Performs a rollback of device configuration.
|
||||||
|
|
||||||
|
Takes:
|
||||||
|
device: Device name
|
||||||
|
device_connection: Device connection object
|
||||||
|
'''
|
||||||
|
try:
|
||||||
|
with open(CONFIG_PATH + device + '_custom_commands.txt') as custom_commands_from_file:
|
||||||
|
command_list_temp = custom_commands_from_file.read().splitlines()
|
||||||
|
|
||||||
|
if os == 'cisco_ios':
|
||||||
|
command_list = ['no ' + command for command in command_list_temp]
|
||||||
|
elif os == 'vyos':
|
||||||
|
command_list = [command.replace('set', 'delete') for command in command_list_temp]
|
||||||
|
|
||||||
|
device_connection.send_config_set(command_list)
|
||||||
|
|
||||||
|
if os == 'vyos':
|
||||||
|
device_connection.send_config_set(['commit', 'save'])
|
||||||
|
elif os == 'cisco_ios':
|
||||||
|
output = device_connection.send_command_timing('copy run start')
|
||||||
|
if 'Destination filename' in output:
|
||||||
|
device_connection.send_command_timing(
|
||||||
|
"\n", strip_prompt=False, strip_command=False
|
||||||
|
)
|
||||||
|
if 'Overwrite the previous' in output:
|
||||||
|
device_connection.send_command_timing(
|
||||||
|
"\n", strip_prompt=False, strip_command=False
|
||||||
|
)
|
||||||
|
if 'Warning: Attempting to overwrite an NVRAM configuration previously written' in output:
|
||||||
|
device_connection.send_command_timing(
|
||||||
|
"\n", strip_prompt=False, strip_command=False
|
||||||
|
)
|
||||||
|
|
||||||
|
log('[{}] Device configuration rolled back'.format(device), 'info')
|
||||||
|
except AttributeError:
|
||||||
|
log('[{}] Could not send commands, device unreachable'.format(device), 'error')
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def delete_rollback_configuration(device):
|
||||||
|
'''
|
||||||
|
Summary:
|
||||||
|
Delete rollback configuration once deployment succeeds..
|
||||||
|
|
||||||
|
Takes:
|
||||||
|
device: Device name
|
||||||
|
'''
|
||||||
|
os.remove(CONFIG_PATH + device + '_rollback.txt')
|
||||||
|
log("[{}] Removed rollback file".format(device), 'info')
|
BIN
ndct/core/configuration_files/._R1_custom_commands.txt
Normal file
BIN
ndct/core/configuration_files/._R1_custom_commands.txt
Normal file
Binary file not shown.
BIN
ndct/core/configuration_files/._R1_generated.txt
Normal file
BIN
ndct/core/configuration_files/._R1_generated.txt
Normal file
Binary file not shown.
BIN
ndct/core/configuration_files/._R2_custom_commands.txt
Normal file
BIN
ndct/core/configuration_files/._R2_custom_commands.txt
Normal file
Binary file not shown.
BIN
ndct/core/configuration_files/._R2_generated.txt
Normal file
BIN
ndct/core/configuration_files/._R2_generated.txt
Normal file
Binary file not shown.
BIN
ndct/core/configuration_files/.___init__.py
Normal file
BIN
ndct/core/configuration_files/.___init__.py
Normal file
Binary file not shown.
2
ndct/core/configuration_files/R1_custom_commands.txt
Normal file
2
ndct/core/configuration_files/R1_custom_commands.txt
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
router ospf 1
|
||||||
|
network 11.11.11.11 0.0.0.0 area 0
|
14
ndct/core/configuration_files/R1_generated.txt
Normal file
14
ndct/core/configuration_files/R1_generated.txt
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
hostname R1
|
||||||
|
!
|
||||||
|
interface Loopback0
|
||||||
|
description Loopback
|
||||||
|
ip address 11.11.11.11 255.255.255.255
|
||||||
|
no shutdown
|
||||||
|
!
|
||||||
|
router bgp 65001
|
||||||
|
neighbor 192.168.21.202 remote-as 65002
|
||||||
|
network 11.11.11.11 mask 255.255.255.255
|
||||||
|
!
|
||||||
|
router ospf 1
|
||||||
|
network 192.168.21.0 0.0.0.255 area 0
|
||||||
|
!
|
1
ndct/core/configuration_files/R2_custom_commands.txt
Normal file
1
ndct/core/configuration_files/R2_custom_commands.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
set protocols ospf area 0 network '12.12.12.12/32'
|
6
ndct/core/configuration_files/R2_generated.txt
Normal file
6
ndct/core/configuration_files/R2_generated.txt
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
set system host-name 'R2'
|
||||||
|
set interfaces loopback lo address '12.12.12.12/32'
|
||||||
|
set interfaces loopback lo description 'Loopback'
|
||||||
|
set protocols bgp 65002 neighbor 192.168.21.201 remote-as '65001'
|
||||||
|
set protocols bgp 65002 network '12.12.12.12/32'
|
||||||
|
set protocols ospf area 0 network '192.168.21.0/24'
|
0
ndct/core/configuration_files/__init__.py
Normal file
0
ndct/core/configuration_files/__init__.py
Normal file
53
ndct/core/connection.py
Normal file
53
ndct/core/connection.py
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
from netmiko import Netmiko
|
||||||
|
from pythonping import ping
|
||||||
|
from ndct.core.device import Device
|
||||||
|
from ndct.core.log import log
|
||||||
|
|
||||||
|
class Connection(Device):
|
||||||
|
def __init__(self, name, ip, username, password, os):
|
||||||
|
'''
|
||||||
|
Takes:
|
||||||
|
ip: IP address of the device
|
||||||
|
username: Username to authentication against
|
||||||
|
password: Password to authenticate with
|
||||||
|
os: Operating system of the device
|
||||||
|
'''
|
||||||
|
super().__init__(name, ip, username, password, os)
|
||||||
|
|
||||||
|
def get_connection(self):
|
||||||
|
'''
|
||||||
|
Summary:
|
||||||
|
Tests device connectivity then creates an SSH connection if successful.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Connection object
|
||||||
|
'''
|
||||||
|
ping_result = ping(self.ip, count=1)
|
||||||
|
|
||||||
|
if 'Request timed out' not in str(ping_result):
|
||||||
|
log('[{}] Reachable, getting connection...'.format(self.name), 'info')
|
||||||
|
|
||||||
|
connection = Netmiko(
|
||||||
|
self.ip,
|
||||||
|
username=self.username,
|
||||||
|
password=self.password,
|
||||||
|
device_type=self.os
|
||||||
|
)
|
||||||
|
|
||||||
|
log('[{}] Connected'.format(self.name), 'info')
|
||||||
|
return connection
|
||||||
|
else:
|
||||||
|
log('[{}] Not reachable'.format(self.name), 'info')
|
||||||
|
return
|
||||||
|
|
||||||
|
def close_connection(self, connection):
|
||||||
|
'''
|
||||||
|
Summary:
|
||||||
|
Closes an SSH connection to a device.
|
||||||
|
|
||||||
|
Takes:
|
||||||
|
connection: Connection object to close
|
||||||
|
'''
|
||||||
|
connection.disconnect()
|
||||||
|
|
||||||
|
log('[{}] Disconnected'.format(self.name), 'info')
|
111
ndct/core/crypt.py
Normal file
111
ndct/core/crypt.py
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
import base64
|
||||||
|
import os
|
||||||
|
import hashlib
|
||||||
|
import json
|
||||||
|
|
||||||
|
from ast import literal_eval
|
||||||
|
from getpass import getpass
|
||||||
|
from cryptography.fernet import Fernet
|
||||||
|
from cryptography.hazmat.backends import default_backend
|
||||||
|
from cryptography.hazmat.primitives import hashes
|
||||||
|
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
|
||||||
|
from ndct.core.paths import KEY_PATH, DB_PATH
|
||||||
|
from ndct.core.log import log
|
||||||
|
|
||||||
|
class Crypt:
|
||||||
|
@staticmethod
|
||||||
|
def generate_key():
|
||||||
|
'''
|
||||||
|
Summary:
|
||||||
|
Generate a key used for symmetric encryption, using the hash of a user entered password for the salt.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Encryption key
|
||||||
|
|
||||||
|
Encryption type:
|
||||||
|
128-bit AES
|
||||||
|
'''
|
||||||
|
if os.path.isfile(KEY_PATH):
|
||||||
|
log("Using existing key '{}' for encryption/decryption".format(KEY_PATH), 'info')
|
||||||
|
key = Crypt.get_key()
|
||||||
|
|
||||||
|
return key
|
||||||
|
else:
|
||||||
|
log("Attempting to use '{}' but no key found, create a new key or add an existing one".format(KEY_PATH), 'info')
|
||||||
|
|
||||||
|
password = getpass(prompt='New encryption key password: ')
|
||||||
|
password_bytes = password.encode()
|
||||||
|
salt = os.urandom(16)
|
||||||
|
kdf = PBKDF2HMAC(
|
||||||
|
algorithm=hashes.SHA256(),
|
||||||
|
length=32,
|
||||||
|
salt=salt,
|
||||||
|
iterations=100000,
|
||||||
|
backend=default_backend()
|
||||||
|
)
|
||||||
|
key = base64.urlsafe_b64encode(kdf.derive(password_bytes))
|
||||||
|
|
||||||
|
with open(KEY_PATH, 'wb') as encryption_key:
|
||||||
|
encryption_key.write(key)
|
||||||
|
|
||||||
|
log("Stored encryption key as '{}'".format(KEY_PATH), 'info')
|
||||||
|
|
||||||
|
return key
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_key():
|
||||||
|
'''
|
||||||
|
Summary:
|
||||||
|
Get stored encryption key.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Encryption key
|
||||||
|
|
||||||
|
Encryption type:
|
||||||
|
128-bit AES
|
||||||
|
'''
|
||||||
|
with open(KEY_PATH, 'rb') as encryption_key:
|
||||||
|
key = encryption_key.read()
|
||||||
|
|
||||||
|
return key
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def create_encrypted_file(filename, data):
|
||||||
|
'''
|
||||||
|
Summary:
|
||||||
|
Create an encrypted file from data that is passed to the function.
|
||||||
|
|
||||||
|
Takes:
|
||||||
|
filename: Name of encrypted file
|
||||||
|
data: Data to encrypt
|
||||||
|
'''
|
||||||
|
key = Crypt.generate_key()
|
||||||
|
|
||||||
|
fernet = Fernet(key)
|
||||||
|
encrypted_data = fernet.encrypt(str(data).encode())
|
||||||
|
|
||||||
|
with open(DB_PATH + filename, 'wb') as encrypted_file:
|
||||||
|
encrypted_file.write(encrypted_data)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_encrypted_file_contents(filename):
|
||||||
|
'''
|
||||||
|
Summary:
|
||||||
|
Get the contents of an encrypted file to enable use of the stored data.
|
||||||
|
|
||||||
|
Takes:
|
||||||
|
filename: Name of file to get decrypted contents of
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Decrypted file contents
|
||||||
|
'''
|
||||||
|
key = Crypt.generate_key()
|
||||||
|
|
||||||
|
with open(DB_PATH + filename, 'rb') as encrypted_file:
|
||||||
|
data = encrypted_file.read()
|
||||||
|
|
||||||
|
fernet = Fernet(key)
|
||||||
|
temp_data = fernet.decrypt(data).decode()
|
||||||
|
temp_data_list = literal_eval(temp_data)
|
||||||
|
|
||||||
|
return temp_data_list
|
BIN
ndct/core/db/.___init__.py
Normal file
BIN
ndct/core/db/.___init__.py
Normal file
Binary file not shown.
BIN
ndct/core/db/._deployments
Normal file
BIN
ndct/core/db/._deployments
Normal file
Binary file not shown.
BIN
ndct/core/db/._devices
Normal file
BIN
ndct/core/db/._devices
Normal file
Binary file not shown.
BIN
ndct/core/db/._example_devices_file.txt
Normal file
BIN
ndct/core/db/._example_devices_file.txt
Normal file
Binary file not shown.
BIN
ndct/core/db/._key.key
Normal file
BIN
ndct/core/db/._key.key
Normal file
Binary file not shown.
0
ndct/core/db/__init__.py
Normal file
0
ndct/core/db/__init__.py
Normal file
1
ndct/core/db/deployments
Normal file
1
ndct/core/db/deployments
Normal file
@ -0,0 +1 @@
|
|||||||
|
gAAAAABevSjq3Y7_nWkDlUWzLjMnKBkn29IiaA-GKTCL-dCdgSm0klcLCWLlYN_lpVTsrIY9QHQpGjZGCWGyMC3YFRDcZC4xxQ8nWgOU26xH5ypa2wBZrqGcCNpktcxRUNDl-NXOy9FOrYVzJFYMNMd_krmAya9NJcKX3AgKlBtKVZQGYpTYDOx4tBJ3roCOKRJEM_ZP5tPCvbI9FdBj4XjoeW1Rzz5i_wIY53k8aKhL8I6FDZXVFtg9s_5ph8I4St43kXVUuViGsbc2J_6R0Nw_xgs0vCMpxFZXgxxP_SAtG-Ux-ApVPjL6MibNbn-qgfzBqf3FaRp8o1N7-hT0FgD2Xv3dreXIbiYKKi8FFjdYtMkP0pB0ZUpES131QfSFoic5X4cQ7Rdi6ml6avVgjitsePLv-eBxDULrT0UrHPEyD_NjPugRNvYWlzaW0NBsyYkveXxsZuZdsQ9mlTcrnNcehCMAnzTvi7hnANhYU2gmraPskCmoe8CsmFNZC82S-jkLfHVo4CAsnnRmcGkJ_VaZWr659Uk9Txj0gBkBP5QVMTnJ1gFjzHjytKUNRVELk4rn4d0NixM3fRCxAGXIxiqnUKlsi-FFabOjrbOaWSmJIbDt64wLkSgGotfvz5j2ilTMArEzWO3xEGh9E4rbWg-b1cczdx2_IW5eh38NIpNImWAGmkhMHunGYGW6VoYRZpln-3X3Okt5eNOW4f1EeG0LroVzy71LgA==
|
1
ndct/core/db/devices
Normal file
1
ndct/core/db/devices
Normal file
@ -0,0 +1 @@
|
|||||||
|
gAAAAABevR-IVmPaNA1MBwD9ukWIWMbf2axg4PgO0AxYxZAl35eaWEiU2Er8Y1Hk__cyc7nZ8l0KQHWy-iJRNuMs2b93_yfmN54cv0NZk9HwpOaWmGv4Ejp2iQLjXvcq_NrX8djlDkTyu73S98yzEjJ9HJR8o2fMbdotW37JCURQyBpn_IzXdfCuN6IXxGClTVK0TBvhVEESYrHOapUKB_qchya42jwTNuam7RgVOl8IkyrJqKsGICKtvNmCxLmm1yik_wO1QLwHUrBPuP1r5jtiFIfY090BobOuERky-Za7-ICkFt8x-YUJAGeimP_ynMNAXRh6l15FVemyHKxyju8tgawtcngAiw==
|
15
ndct/core/db/example_devices_file.txt
Normal file
15
ndct/core/db/example_devices_file.txt
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
R7
|
||||||
|
10.1.1.1
|
||||||
|
admin
|
||||||
|
password
|
||||||
|
cisco_ios
|
||||||
|
R8
|
||||||
|
10.2.2.2
|
||||||
|
admin
|
||||||
|
password
|
||||||
|
cisco_ios
|
||||||
|
R9
|
||||||
|
10.3.3.3
|
||||||
|
admin
|
||||||
|
password
|
||||||
|
vyos
|
1
ndct/core/db/key.key
Normal file
1
ndct/core/db/key.key
Normal file
@ -0,0 +1 @@
|
|||||||
|
ENR0-Y8N5ZH7ho6DEAI9xRe17jwVAXsuekLOEfED6Ic=
|
103
ndct/core/deployment.py
Normal file
103
ndct/core/deployment.py
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
import uuid
|
||||||
|
import os
|
||||||
|
|
||||||
|
from multiprocessing import Process
|
||||||
|
from ndct.core.configuration import Configuration
|
||||||
|
from ndct.core.crypt import Crypt
|
||||||
|
from ndct.core.log import log
|
||||||
|
from ndct.core.paths import DB_PATH
|
||||||
|
|
||||||
|
deployments = []
|
||||||
|
|
||||||
|
class Deployment:
|
||||||
|
def __init__(self, name, targets, action, deployment_id=str(uuid.uuid4()), status='Not started'):
|
||||||
|
'''
|
||||||
|
Takes:
|
||||||
|
name: Deployment name
|
||||||
|
targets: Target devices
|
||||||
|
action: Action of deployment
|
||||||
|
deployment_id: Unique identifier of deployment
|
||||||
|
status: Deployment status
|
||||||
|
attribute: Deployment attribute - for 'get' action deployments
|
||||||
|
'''
|
||||||
|
self.name = name
|
||||||
|
self.targets = targets
|
||||||
|
self.action = action
|
||||||
|
self.deployment_id = deployment_id
|
||||||
|
self.status = status
|
||||||
|
|
||||||
|
def all(self):
|
||||||
|
'''
|
||||||
|
Summary:
|
||||||
|
Gets the contents of a Deployment instance.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Deployment instance contents in dictionairy form
|
||||||
|
'''
|
||||||
|
return self.__dict__
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
'''
|
||||||
|
Summary:
|
||||||
|
Runs a deployment.
|
||||||
|
'''
|
||||||
|
log('[{}] Running deployment'.format(self.name), 'info')
|
||||||
|
|
||||||
|
self.status = 'In progress'
|
||||||
|
log('[{}] Updated deployment status to In progress'.format(self.name), 'info')
|
||||||
|
|
||||||
|
if self.action == 'get':
|
||||||
|
device_processes = [Process(target=Configuration.get_configuration, args=(target_device,)) for target_device in self.targets]
|
||||||
|
elif self.action == 'deploy_generated':
|
||||||
|
device_processes = [Process(target=Configuration.deploy_generated_configuration, args=(target_device,)) for target_device in self.targets]
|
||||||
|
elif self.action == 'deploy_custom':
|
||||||
|
device_processes = [Process(target=Configuration.deploy_custom_configuration, args=(target_device,)) for target_device in self.targets]
|
||||||
|
|
||||||
|
for _process in device_processes:
|
||||||
|
_process.start()
|
||||||
|
|
||||||
|
for _process in device_processes:
|
||||||
|
_process.join()
|
||||||
|
|
||||||
|
self.status = 'Completed'
|
||||||
|
log('[{}] Updated deployment status to Completed'.format(self.name), 'info')
|
||||||
|
|
||||||
|
log('[{}] Deployment completed'.format(self.name), 'info')
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_deployments_from_file():
|
||||||
|
'''
|
||||||
|
Summary:
|
||||||
|
Gets deployment data from an encrypted file and creates an object stored in the deployments list.
|
||||||
|
'''
|
||||||
|
if os.path.isfile(DB_PATH + 'deployments'):
|
||||||
|
deployments_temp_file = Crypt.get_encrypted_file_contents('deployments')
|
||||||
|
for deployment in deployments_temp_file:
|
||||||
|
deployment_object = Deployment(
|
||||||
|
deployment['name'],
|
||||||
|
deployment['targets'],
|
||||||
|
deployment['action'],
|
||||||
|
deployment_id=deployment['deployment_id'],
|
||||||
|
status=deployment['status'],
|
||||||
|
)
|
||||||
|
deployments.append({deployment['name']: deployment_object})
|
||||||
|
|
||||||
|
log('Got deployments from file', 'info')
|
||||||
|
else:
|
||||||
|
log('No deployments to get from file', 'info')
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def save_deployments_to_file():
|
||||||
|
'''
|
||||||
|
Summary:
|
||||||
|
Saves deployment data to an encrypted file.
|
||||||
|
'''
|
||||||
|
deployments_to_save = []
|
||||||
|
|
||||||
|
for deployment in deployments:
|
||||||
|
for deployment_name, deployment_object in deployment.items():
|
||||||
|
deployments_to_save.append(deployment_object.all())
|
||||||
|
|
||||||
|
Crypt.create_encrypted_file('deployments', deployments_to_save)
|
||||||
|
|
||||||
|
log('Saved deployments to file', 'info')
|
86
ndct/core/device.py
Normal file
86
ndct/core/device.py
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
import os
|
||||||
|
|
||||||
|
from ndct.core.crypt import Crypt
|
||||||
|
from ndct.core.log import log
|
||||||
|
from ndct.core.paths import DB_PATH
|
||||||
|
|
||||||
|
devices = []
|
||||||
|
|
||||||
|
class Device():
|
||||||
|
def __init__(self, name, ip, username, password, os):
|
||||||
|
'''
|
||||||
|
Takes:
|
||||||
|
name: Device name
|
||||||
|
ip: IP address of the device
|
||||||
|
user: Username to use for device connection
|
||||||
|
password: Password to use for device connection authentication
|
||||||
|
os: Operating system of the device
|
||||||
|
'''
|
||||||
|
self.name = name
|
||||||
|
self.ip = ip
|
||||||
|
self.username = username
|
||||||
|
self.password = password
|
||||||
|
self.os = os
|
||||||
|
|
||||||
|
def all(self):
|
||||||
|
'''
|
||||||
|
Summary:
|
||||||
|
Gets the contents of a Device instance.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Device instance contents in dictionairy form
|
||||||
|
'''
|
||||||
|
return self.__dict__
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_devices_from_file():
|
||||||
|
'''
|
||||||
|
Summary:
|
||||||
|
Gets device data from an encrypted file and creates an object stored in the devices list.
|
||||||
|
'''
|
||||||
|
if os.path.isfile(DB_PATH + 'devices'):
|
||||||
|
devices_temp_file = Crypt.get_encrypted_file_contents('devices')
|
||||||
|
for device in devices_temp_file:
|
||||||
|
device_object = Device(
|
||||||
|
device['name'],
|
||||||
|
device['ip'],
|
||||||
|
device['username'],
|
||||||
|
device['password'],
|
||||||
|
device['os']
|
||||||
|
)
|
||||||
|
devices.append({device['name']: device_object})
|
||||||
|
log('Got devices from file', 'info')
|
||||||
|
else:
|
||||||
|
log('No devices to get from file', 'info')
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def save_devices_to_file():
|
||||||
|
'''
|
||||||
|
Summary:
|
||||||
|
Saves device data to an encrypted file.
|
||||||
|
'''
|
||||||
|
devices_to_save = []
|
||||||
|
|
||||||
|
for device in devices:
|
||||||
|
for device_name, device_object in device.items():
|
||||||
|
devices_to_save.append(device_object.all())
|
||||||
|
|
||||||
|
Crypt.create_encrypted_file('devices', devices_to_save)
|
||||||
|
|
||||||
|
log('Saved devices to file', 'info')
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_device_information(device_name):
|
||||||
|
'''
|
||||||
|
Summary:
|
||||||
|
Gets the contents of a Device instance.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Device instance contents in dictionairy form.
|
||||||
|
'''
|
||||||
|
for device in devices:
|
||||||
|
if device_name in device:
|
||||||
|
device_information = device[device_name].all()
|
||||||
|
return device_information
|
||||||
|
|
||||||
|
return None
|
BIN
ndct/core/device_metadata/._R1_metadata.yaml
Normal file
BIN
ndct/core/device_metadata/._R1_metadata.yaml
Normal file
Binary file not shown.
BIN
ndct/core/device_metadata/._R2_metadata.yaml
Normal file
BIN
ndct/core/device_metadata/._R2_metadata.yaml
Normal file
Binary file not shown.
BIN
ndct/core/device_metadata/.___init__.py
Normal file
BIN
ndct/core/device_metadata/.___init__.py
Normal file
Binary file not shown.
BIN
ndct/core/device_metadata/._cisco_ios_example.yaml
Normal file
BIN
ndct/core/device_metadata/._cisco_ios_example.yaml
Normal file
Binary file not shown.
BIN
ndct/core/device_metadata/._vyos_example.yaml
Normal file
BIN
ndct/core/device_metadata/._vyos_example.yaml
Normal file
Binary file not shown.
26
ndct/core/device_metadata/R1_metadata.yaml
Normal file
26
ndct/core/device_metadata/R1_metadata.yaml
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
R1:
|
||||||
|
hostname: R1
|
||||||
|
interfaces:
|
||||||
|
Loopback0:
|
||||||
|
description: Loopback
|
||||||
|
ip_address: 11.11.11.11
|
||||||
|
subnet_mask: 255.255.255.255
|
||||||
|
|
||||||
|
bgp:
|
||||||
|
as: 65001
|
||||||
|
peers:
|
||||||
|
R2:
|
||||||
|
neighbor_ip: 192.168.21.202
|
||||||
|
neighbor_as: 65002
|
||||||
|
networks:
|
||||||
|
network1:
|
||||||
|
subnet: 11.11.11.11
|
||||||
|
subnet_mask: 255.255.255.255
|
||||||
|
|
||||||
|
ospf:
|
||||||
|
process: 1
|
||||||
|
networks:
|
||||||
|
network1:
|
||||||
|
area: 0
|
||||||
|
subnet: 192.168.21.0
|
||||||
|
wildcard_mask: 0.0.0.255
|
25
ndct/core/device_metadata/R2_metadata.yaml
Normal file
25
ndct/core/device_metadata/R2_metadata.yaml
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
R2:
|
||||||
|
hostname: R2
|
||||||
|
interfaces:
|
||||||
|
lo:
|
||||||
|
description: Loopback
|
||||||
|
ip_address: 12.12.12.12
|
||||||
|
subnet_mask: 32
|
||||||
|
|
||||||
|
bgp:
|
||||||
|
as: 65002
|
||||||
|
peers:
|
||||||
|
R1:
|
||||||
|
neighbor_ip: 192.168.21.201
|
||||||
|
neighbor_as: 65001
|
||||||
|
networks:
|
||||||
|
network1:
|
||||||
|
subnet: 12.12.12.12
|
||||||
|
subnet_mask: 32
|
||||||
|
|
||||||
|
ospf:
|
||||||
|
networks:
|
||||||
|
network1:
|
||||||
|
area: 0
|
||||||
|
subnet: 192.168.21.0
|
||||||
|
subnet_mask: 24
|
0
ndct/core/device_metadata/__init__.py
Normal file
0
ndct/core/device_metadata/__init__.py
Normal file
26
ndct/core/device_metadata/cisco_ios_example.yaml
Normal file
26
ndct/core/device_metadata/cisco_ios_example.yaml
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
ROUTER:
|
||||||
|
hostname: X
|
||||||
|
interfaces:
|
||||||
|
int1:
|
||||||
|
description: X
|
||||||
|
ip_address: X
|
||||||
|
subnet_mask: X
|
||||||
|
|
||||||
|
bgp:
|
||||||
|
as: X
|
||||||
|
peers:
|
||||||
|
X:
|
||||||
|
neighbor_ip: X
|
||||||
|
neighbor_as: X
|
||||||
|
networks:
|
||||||
|
network1:
|
||||||
|
subnet: X
|
||||||
|
subnet_mask: X
|
||||||
|
|
||||||
|
ospf:
|
||||||
|
process: X
|
||||||
|
networks:
|
||||||
|
network1:
|
||||||
|
area: X
|
||||||
|
subnet: X
|
||||||
|
wildcard_mask: X
|
25
ndct/core/device_metadata/vyos_example.yaml
Normal file
25
ndct/core/device_metadata/vyos_example.yaml
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
ROUTER:
|
||||||
|
hostname: X
|
||||||
|
interfaces:
|
||||||
|
int1:
|
||||||
|
description: X
|
||||||
|
ip_address: X
|
||||||
|
subnet_mask: X
|
||||||
|
|
||||||
|
bgp:
|
||||||
|
as: X
|
||||||
|
peers:
|
||||||
|
X:
|
||||||
|
neighbor_ip: X
|
||||||
|
neighbor_as: X
|
||||||
|
networks:
|
||||||
|
network1:
|
||||||
|
subnet: X
|
||||||
|
subnet_mask: X
|
||||||
|
|
||||||
|
ospf:
|
||||||
|
networks:
|
||||||
|
network1:
|
||||||
|
area: X
|
||||||
|
subnet: X
|
||||||
|
subnet_mask: X
|
42
ndct/core/log.py
Normal file
42
ndct/core/log.py
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
import logging
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
|
||||||
|
from datetime import datetime
|
||||||
|
from ndct.core.paths import LOGGING_PATH
|
||||||
|
|
||||||
|
def log(log_message, level):
|
||||||
|
'''
|
||||||
|
Summary:
|
||||||
|
Logs information to logfile at the specified level.
|
||||||
|
|
||||||
|
Takes:
|
||||||
|
log_message: Information to log
|
||||||
|
level: Level of which to log the information at
|
||||||
|
'''
|
||||||
|
logger = logging.getLogger('ndct-logger')
|
||||||
|
|
||||||
|
log_message_types = {
|
||||||
|
'debug': logger.debug,
|
||||||
|
'info': logger.info,
|
||||||
|
'warning': logger.warning,
|
||||||
|
'error': logger.error,
|
||||||
|
'critical': logger.critical
|
||||||
|
}
|
||||||
|
|
||||||
|
if not logger.handlers:
|
||||||
|
logger.setLevel(logging.DEBUG)
|
||||||
|
|
||||||
|
formatter = logging.Formatter('%(asctime)s [%(levelname)s] %(message)s')
|
||||||
|
|
||||||
|
file_handler = logging.FileHandler(LOGGING_PATH + 'log_' + datetime.now().strftime("%Y-%m-%d_%H:%M:%S") + '.log')
|
||||||
|
file_handler.setLevel(logging.DEBUG)
|
||||||
|
file_handler.setFormatter(formatter)
|
||||||
|
|
||||||
|
stream_handler = logging.StreamHandler(sys.stdout)
|
||||||
|
stream_handler.setFormatter(formatter)
|
||||||
|
|
||||||
|
logger.addHandler(file_handler)
|
||||||
|
logger.addHandler(stream_handler)
|
||||||
|
|
||||||
|
log_message_types[level](log_message)
|
BIN
ndct/core/logs/.___init__.py
Normal file
BIN
ndct/core/logs/.___init__.py
Normal file
Binary file not shown.
0
ndct/core/logs/__init__.py
Normal file
0
ndct/core/logs/__init__.py
Normal file
19
ndct/core/paths.py
Normal file
19
ndct/core/paths.py
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import os
|
||||||
|
|
||||||
|
current_path = os.path.dirname(__file__)
|
||||||
|
|
||||||
|
#For compatability with any operating system
|
||||||
|
#KEY_PATH = current_path + '/db/key.key'
|
||||||
|
#MODULE_PATH = os.path.dirname(current_path) + '/modules/'
|
||||||
|
#CONFIG_PATH = current_path + '/configuration_files/'
|
||||||
|
#METADATA_PATH = current_path + '/device_metadata/'
|
||||||
|
#LOGGING_PATH = current_path + '/logs/'
|
||||||
|
#DB_PATH = current_path + '/db/'
|
||||||
|
|
||||||
|
#For testing only
|
||||||
|
KEY_PATH = 'Documents/Python/NDCT/ndct/core/db/key.key'
|
||||||
|
MODULE_PATH = 'Documents/Python/NDCT/ndct/modules/'
|
||||||
|
CONFIG_PATH = 'Documents/Python/NDCT/ndct/core/configuration_files/'
|
||||||
|
METADATA_PATH = 'Documents/Python/NDCT/ndct/core/device_metadata/'
|
||||||
|
LOGGING_PATH = 'Documents/Python/NDCT/ndct/core/logs/'
|
||||||
|
DB_PATH = 'Documents/Python/NDCT/ndct/core/db/'
|
BIN
ndct/modules/.___init__.py
Normal file
BIN
ndct/modules/.___init__.py
Normal file
Binary file not shown.
BIN
ndct/modules/._cisco_ios
Executable file
BIN
ndct/modules/._cisco_ios
Executable file
Binary file not shown.
BIN
ndct/modules/._vyos
Executable file
BIN
ndct/modules/._vyos
Executable file
Binary file not shown.
0
ndct/modules/__init__.py
Normal file
0
ndct/modules/__init__.py
Normal file
BIN
ndct/modules/cisco_ios/.___init__.py
Normal file
BIN
ndct/modules/cisco_ios/.___init__.py
Normal file
Binary file not shown.
BIN
ndct/modules/cisco_ios/._commands.json
Normal file
BIN
ndct/modules/cisco_ios/._commands.json
Normal file
Binary file not shown.
BIN
ndct/modules/cisco_ios/._template.j2
Normal file
BIN
ndct/modules/cisco_ios/._template.j2
Normal file
Binary file not shown.
0
ndct/modules/cisco_ios/__init__.py
Normal file
0
ndct/modules/cisco_ios/__init__.py
Normal file
7
ndct/modules/cisco_ios/commands.json
Normal file
7
ndct/modules/cisco_ios/commands.json
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"commands":
|
||||||
|
{
|
||||||
|
"config": "show run",
|
||||||
|
"save_config": "copy run start"
|
||||||
|
}
|
||||||
|
}
|
28
ndct/modules/cisco_ios/template.j2
Normal file
28
ndct/modules/cisco_ios/template.j2
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
hostname {{hostname}}
|
||||||
|
!
|
||||||
|
{% if interfaces %}
|
||||||
|
{% for interface, config in interfaces.items() %}
|
||||||
|
interface {{interface}}
|
||||||
|
description {{config['description']}}
|
||||||
|
ip address {{config['ip_address']}} {{config['subnet_mask']}}
|
||||||
|
no shutdown
|
||||||
|
!
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
{% if bgp %}
|
||||||
|
router bgp {{bgp['as']}}
|
||||||
|
{% for peer, peer_config in bgp['peers'].items() %}
|
||||||
|
neighbor {{peer_config['neighbor_ip']}} remote-as {{peer_config['neighbor_as']}}
|
||||||
|
{% endfor %}
|
||||||
|
{% for network, network_config in bgp['networks'].items() %}
|
||||||
|
network {{network_config['subnet']}} mask {{network_config['subnet_mask']}}
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
!
|
||||||
|
{% if ospf %}
|
||||||
|
router ospf {{ospf['process']}}
|
||||||
|
{% for network, network_config in ospf['networks'].items() %}
|
||||||
|
network {{network_config['subnet']}} {{network_config['wildcard_mask']}} area {{network_config['area']}}
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
!
|
BIN
ndct/modules/vyos/.___init__.py
Normal file
BIN
ndct/modules/vyos/.___init__.py
Normal file
Binary file not shown.
BIN
ndct/modules/vyos/._commands.json
Normal file
BIN
ndct/modules/vyos/._commands.json
Normal file
Binary file not shown.
BIN
ndct/modules/vyos/._template.j2
Normal file
BIN
ndct/modules/vyos/._template.j2
Normal file
Binary file not shown.
0
ndct/modules/vyos/__init__.py
Normal file
0
ndct/modules/vyos/__init__.py
Normal file
7
ndct/modules/vyos/commands.json
Normal file
7
ndct/modules/vyos/commands.json
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"commands":
|
||||||
|
{
|
||||||
|
"config": "show configuration commands",
|
||||||
|
"save_config": ["commit", "save", "exit"]
|
||||||
|
}
|
||||||
|
}
|
20
ndct/modules/vyos/template.j2
Normal file
20
ndct/modules/vyos/template.j2
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
set system host-name '{{hostname}}'
|
||||||
|
{% if interfaces %}
|
||||||
|
{% for interface, interface_config in interfaces.items() %}
|
||||||
|
set interfaces {% if interface[0:2] == 'lo' %}loopback{% elif interface[0:3] == 'eth' %}ethernet{% endif %} {{interface}} address '{{interface_config['ip_address']}}/{{interface_config['subnet_mask']}}'
|
||||||
|
set interfaces {% if interface[0:2] == 'lo' %}loopback{% elif interface[0:3] == 'eth' %}ethernet{% endif %} {{interface}} description '{{interface_config['description']}}'
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
{% if bgp %}
|
||||||
|
{% for peer, peer_config in bgp['peers'].items() %}
|
||||||
|
set protocols bgp {{bgp['as']}} neighbor {{peer_config['neighbor_ip']}} remote-as '{{peer_config['neighbor_as']}}'
|
||||||
|
{% endfor %}
|
||||||
|
{% for network, network_config in bgp['networks'].items() %}
|
||||||
|
set protocols bgp {{bgp['as']}} network '{{network_config['subnet']}}/{{network_config['subnet_mask']}}'
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
{% if ospf %}
|
||||||
|
{% for network, network_config in ospf['networks'].items() %}
|
||||||
|
set protocols ospf area {{network_config['area']}} network '{{network_config['subnet']}}/{{network_config['subnet_mask']}}'
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
29
setup.py
Normal file
29
setup.py
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
from setuptools import setup, find_packages
|
||||||
|
|
||||||
|
setup(
|
||||||
|
name='ndct',
|
||||||
|
|
||||||
|
# Information
|
||||||
|
version='1.0',
|
||||||
|
description='A configuration management tool for network device orchestration.',
|
||||||
|
long_description='A configuration management tool for network device orchestration through the use of deployments.',
|
||||||
|
|
||||||
|
# Author details
|
||||||
|
author='Dominic James Macfarlane',
|
||||||
|
author_email='m021859g@student.staffs.ac.uk',
|
||||||
|
|
||||||
|
# Packages
|
||||||
|
packages=find_packages(exclude=[""]),
|
||||||
|
|
||||||
|
package_data={
|
||||||
|
"": ["*.json", "*.j2", "*.yaml", "*.txt"],
|
||||||
|
},
|
||||||
|
|
||||||
|
# Dependencies
|
||||||
|
install_requires=['netmiko>=2.3.3', 'Jinja2>=2.10', 'cryptography>=2.6.1', 'Click>=7.0', 'PyYAML>=5.1.2', 'pythonping>=1.0.5'],
|
||||||
|
|
||||||
|
# Entry points
|
||||||
|
entry_points={
|
||||||
|
'console_scripts': ['ndct = ndct.cli.main:main']
|
||||||
|
}
|
||||||
|
)
|
Loading…
x
Reference in New Issue
Block a user