This commit is contained in:
2025-11-27 11:59:36 +01:00
parent 39f8b87743
commit 558a4c30f7
15 changed files with 1525 additions and 0 deletions

View File

@@ -0,0 +1,106 @@
from flask import Flask,jsonify,request,abort, make_response
from werkzeug.exceptions import HTTPException
from nornir import InitNornir
import json
ALLOWED_EXTENSIONS = {'conf'}
UPLOAD_FOLDER = 'fastprod/upload_files/'
def init_nornir():
app.config['nr'] = InitNornir(config_file="fastprod/inventory/config.yaml")
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
def allowed_file(filename):
return '.' in filename and \
filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
app = Flask(__name__)
from services.devices import ( get_inventory, add_device,get_device_by_name,delete_device,get_device_interfaces,get_device_interfaces_ip,get_device_technical_info)
from services.config import (get_config_by_device)
from services.tasks import (run_show_commands_by_device,run_config_commands_by_device)
init_nornir()
@app.route("/")
def hello_world():
return jsonify({
"env": "DEV",
"name": "fastprod_backend",
"version": 1.0
})
@app.route("/devices", methods=['GET', 'POST'])
def devices():
if request.method == 'GET':
init_nornir()
devices = get_inventory()
return jsonify(devices=devices, total_count=len(devices))
if request.method == 'POST':
data = request.get_json()
new_device = add_device(data)
return jsonify(device=new_device)
@app.route("/devices/<device_name>", methods=['GET', 'DELETE'])
def device_by_name(device_name):
if request.method == 'GET':
device = get_device_by_name(device_name)
return jsonify(device=device)
if request.method == 'DELETE':
device = get_device_by_name(device_name)
delete_device(device)
return jsonify(message="Device deleted")
@app.route("/devices/<device_name>/interfaces", methods=['GET'])
def get_interfaces(device_name):
device = get_device_by_name(device_name)
interfaces = get_device_interfaces(device)
return jsonify(interfaces=interfaces)
@app.route("/devices/<device_name>/interfaces/ip", methods=['GET'])
def get_interfaces_ip(device_name):
if request.method == 'GET':
device = get_device_by_name(device_name)
interfaces_ip = get_device_interfaces_ip(device)
return jsonify(interfaces_ip=interfaces_ip)
@app.route("/devices/<device_name>/facts", methods=['GET'])
def get_technical_info(device_name):
if request.method == 'GET':
device = get_device_by_name(device_name)
technical_info = get_device_technical_info(device)
return jsonify(interfaces_ip=technical_info)
@app.route("/devices/<device_name>/config", methods=['GET','POST'])
def get_config(device_name):
device = get_device_by_name(device_name)
if request.method == 'GET':
config = get_config_by_device(device)
return jsonify(interfaces_ip=config)
if request.method == 'POST':
if request.get_json().get('mode') == 'enable':
commands = request.get_json().get('commands')
result = run_show_commands_by_device(device, commands=commands)
return jsonify(result=result)
if request.get_json().get('mode') == 'config':
commands = request.get_json().get('commands')
result = run_config_commands_by_device(device, commands=commands)
return jsonify(result=result, commands=commands)
@app.errorhandler(HTTPException)
def handle_exception(e):
"""Return JSON instead of HTML for HTTP errors."""
# start with the correct headers and status code from the error
response = e.get_response()
# replace the body with JSON
response.data = json.dumps({
"code": e.code,
"name": e.name,
"description": e.description,
})
response.content_type = "application/json"
return response

View File

@@ -0,0 +1,10 @@
inventory:
plugin: SimpleInventory
options:
host_file: "fastprod/inventory/hosts.yaml"
group_file: "fastprod/inventory/groups.yaml"
defaults_file: "fastprod/inventory/defaults.yaml"
runner:
plugin: threaded
options:
num_workers: 20

View File

@@ -0,0 +1,2 @@
username: cisco
password: cisco

View File

@@ -0,0 +1,4 @@
ios:
platform: ios
data:
vendor: Cisco

View File

@@ -0,0 +1,67 @@
ESW1-CPE-BAT-A:
data:
building: A
device_model: C3725
device_name: ESW1-CPE-BAT-A
device_type: router_switch
locality: lyon
groups:
- ios
hostname: 172.16.100.123
port: 22
ESW1-CPE-BAT-B:
data:
building: B
device_model: C3725
device_name: ESW1-CPE-BAT-B
device_type: router_switch
locality: lyon
groups:
- ios
hostname: 172.16.100.187
port: 22
R1-CPE-BAT-A:
data:
building: A
device_model: C7200
device_name: R1-CPE-BAT-A
device_type: router
locality: lyon
room: 1
groups:
- ios
hostname: 172.16.100.125
port: 22
R1-CPE-BAT-B:
data:
building: B
device_model: C7200
device_name: R1-CPE-BAT-B
device_type: router
locality: lyon
groups:
- ios
hostname: 172.16.100.189
port: 22
R2-CPE-BAT-A:
data:
building: A
device_model: C7200
device_name: R2-CPE-BAT-A
device_type: router
locality: lyon
groups:
- ios
hostname: 172.16.100.126
port: 22
R2-CPE-BAT-B:
data:
building: B
device_model: C7200
device_name: R2-CPE-BAT-B
device_type: router
locality: lyon
groups:
- ios
hostname: 172.16.100.190
port: 22

View File

@@ -0,0 +1,18 @@
from api import app
from nornir_napalm.plugins.tasks import napalm_get, napalm_configure, napalm_cli
from nornir.core.task import Task, Result
from nornir_utils.plugins.functions import print_result
def get_config_by_device(device):
nr = app.config.get('nr')
result = nr.filter(device_name=device.get('name')).run(task=napalm_get, getters=["get_config"])
return result[device.get('name')][0].result.get("get_config")
def run_config_from_file_by_device(device=None, file_path=None):
nr = app.config.get('nr')
if device and file_path:
result = nr.filter(device_name=device.get('name')).run(task=netmiko_send_config,
config_file=file_path)
print_result(result)
return result[device.get('name')].changed

View File

@@ -0,0 +1,70 @@
from api import app
from flask import Flask,jsonify,request,abort, make_response
from utils.inventory import *
from nornir import InitNornir
from nornir_utils.plugins.functions import print_result
from nornir_napalm.plugins.tasks import napalm_get,napalm_configure, napalm_cli
from nornir_netmiko.tasks import netmiko_send_config,netmiko_send_command, netmiko_save_config, netmiko_commit
def get_inventory():
hosts = app.config.get('nr').inventory.dict().get('hosts').keys()
return list(map(lambda item: app.config.get('nr').inventory.dict().get('hosts').get(item), hosts))
def add_device(device):
device = add_item_to_hosts_yaml(item=device)
return device
def get_device_by_name(name):
host = app.config.get('nr').inventory.hosts.get(name)
if not host:
abort(make_response(jsonify(message="device not found"), 404))
return host.dict()
def delete_device(device):
try:
delete_item_from_hosts_yaml(item=device)
except Exception as e:
abort(make_response(jsonify(message="Unable to delete this device", error=str(e)), 500))
def get_device_interfaces(device):
nr = app.config.get('nr')
result = nr.filter(device_name=device.get('name')).run(task=napalm_get,
getters=["get_interfaces"])
print_result(result)
print(result)
interfaces = result[device.get('name')][0].result.get('get_interfaces')
return list(map(lambda item: dict(name=item, **interfaces.get(item)), interfaces))
def get_device_technical_info(device):
nr = app.config.get('nr')
result = nr.filter(device_name=device.get('name')).run(
task=napalm_get,
getters=["get_facts"]
)
print_result(result)
facts = result[device.get('name')][0].result.get('get_facts')
return {
"hostname": facts.get("hostname"),
"vendor": facts.get("vendor"),
"model": facts.get("model"),
"serial_number": facts.get("serial_number"),
"os_version": facts.get("os_version"),
"uptime": facts.get("uptime"),
}
def get_device_interfaces_ip(device):
nr = app.config.get('nr')
result = nr.filter(device_name=device.get('name')).run(task=napalm_get,
getters=["get_interfaces_ip"])
print_result(result)
interfaces = result[device.get('name')][0].result.get('get_interfaces_ip')
def transformer(item):
ip_address = list(interfaces.get(item).get('ipv4').keys())[0]
prefix_length = interfaces.get(item).get('ipv4').get(list(interfaces.get(item).get('ipv4').keys())[0]).get('prefix_length')
return dict(name=item, ip=ip_address, prefix_length=prefix_length)
return list(map(lambda item: transformer(item), interfaces))
#return interfaces

View File

@@ -0,0 +1,35 @@
from api import app
from nornir_napalm.plugins.tasks import napalm_get, napalm_configure, napalm_cli
from nornir.core.task import Task, Result
from nornir_utils.plugins.functions import print_result
from nornir_netmiko.tasks import netmiko_send_config, netmiko_send_command, netmiko_save_config,netmiko_commit
def run_show_commands_by_device(device=None, commands=[]):
nr = app.config.get('nr')
commands_sent = []
if device:
if len(commands) > 1:
for command in commands:
result = nr.filter(device_name=device.get('name')).run(task=napalm_cli,
commands=[command])
print_result(result)
output = result[device.get('name')][0].result.get(command)
commands_sent.append(dict(command=command, result=output))
output = commands_sent
else:
result = nr.filter(device_name=device.get('name')).run(task=napalm_cli,
commands=commands)
output = result[device.get('name')][0].result.get(commands[0])
commands_sent.append(dict(command=commands[0], result= output))
output = commands_sent
return output
def run_config_commands_by_device(device=None, commands=[]):
nr = app.config.get('nr')
commands_sent = []
if device:
result = nr.filter(device_name=device.get('name')).run(task=netmiko_send_config,
config_commands=[commands])
print_result(result)
return result[device.get('name')].changed

View File

@@ -0,0 +1,28 @@
import yaml
def update_hosts_yaml(file_path="fastprod/inventory/hosts.yaml", items=None):
if items:
with open(file_path, 'w') as yaml_file:
yaml.safe_dump(items, yaml_file)
return True
def add_item_to_hosts_yaml(file_path="fastprod/inventory/hosts.yaml", save=True, item=None):
with open(file_path, 'r') as yaml_file:
hosts = yaml.safe_load(yaml_file)
new_hosts = hosts.copy()
new_hosts[item.get('data').get('device_name')] = item
if save:
update_hosts_yaml(items=new_hosts)
return new_hosts[item.get('data').get('device_name')]
def delete_item_from_hosts_yaml(yaml_file_path="fastprod/inventory/hosts.yaml", save=True,item=None, ):
with open(yaml_file_path,'r') as yaml_file:
hosts = yaml.safe_load(yaml_file)
new_hosts = hosts.copy()
del new_hosts[item.get('data').get('device_name')]
if save:
update_hosts_yaml(items=new_hosts)
return True