Collection of CheckMK checks (see https://checkmk.com/). All checks and plugins are provided as is. Absolutely no warranty. Send any comments to thl-cmk[at]outlook[dot]com

Skip to content
Snippets Groups Projects
Commit 3ef91398 authored by thl-cmk's avatar thl-cmk :flag_na:
Browse files

cleanup: removed global variables

parent c214b7b8
No related branches found
No related tags found
No related merge requests found
...@@ -26,13 +26,13 @@ models = [] ...@@ -26,13 +26,13 @@ models = []
version = "17.6.4" version = "17.6.4"
md5 = "2caa962f5ed0ecc52f99b90c733c54de" md5 = "2caa962f5ed0ecc52f99b90c733c54de"
size = 706565772 size = 706565772
models = [] models = ["C1117-4PMLTEEAWE"]
["c1100-universalk9.17.10.01a.SPA.bin"] ["c1100-universalk9.17.10.01a.SPA.bin"]
version = "17.10.1a" version = "17.10.1a"
md5 = "a0cd6218c42f19bed425e3c63a11bcda" md5 = "a0cd6218c42f19bed425e3c63a11bcda"
size = 689542648 size = 689542648
models = ["C1117-4PMLTEEAWE"] models = []
["c3560cx-universalk9-mz.152-7.E7.bin"] ["c3560cx-universalk9-mz.152-7.E7.bin"]
version = "15.2(7)E7" version = "15.2(7)E7"
......
...@@ -29,20 +29,25 @@ ...@@ -29,20 +29,25 @@
# integrated status_debug.html with status.html # integrated status_debug.html with status.html
# 2023-01-29: rework of status page, make table body scrollable # 2023-01-29: rework of status page, make table body scrollable
# 2023-02-01: added cli options, changed debug/log output # 2023-02-01: added cli options, changed debug/log output
# cleanup: removed global variables
# #
# pip install flask xmltodict requests ifaddr tomli # pip install flask xmltodict requests ifaddr tomli
# #
# system libs
import logging import logging
from logging.handlers import RotatingFileHandler from logging.handlers import RotatingFileHandler
from re import compile as re_compile from re import compile as re_compile
from pathlib import Path
from time import strftime from time import strftime
from typing import Optional, List, Dict, Any from typing import Optional, List, Dict, Any
from requests import head from requests import head
from sys import stdout from sys import stdout
import argparse from argparse import (
Namespace as arg_Namespace,
ArgumentParser,
)
# additional libs
from flask import Flask, request, send_from_directory, render_template, Response, redirect, cli from flask import Flask, request, send_from_directory, render_template, Response, redirect, cli
from xmltodict import parse as xml_parse from xmltodict import parse as xml_parse
from ifaddr import get_adapters from ifaddr import get_adapters
...@@ -50,13 +55,10 @@ from tomli import load as toml_load ...@@ -50,13 +55,10 @@ from tomli import load as toml_load
from tomli import TOMLDecodeError from tomli import TOMLDecodeError
# define global variables
IMAGES: Optional[Dict[str, any]] = None # {}
class Settings: class Settings:
def __init__( def __init__(
self, self,
cli_args: Dict[str, any],
cfg_file: Optional[str] = 'open-pnp.toml', cfg_file: Optional[str] = 'open-pnp.toml',
image_data: Optional[str] = 'images.toml', image_data: Optional[str] = 'images.toml',
bind_pnp_server: Optional[str] = '0.0.0.0', bind_pnp_server: Optional[str] = '0.0.0.0',
...@@ -85,13 +87,23 @@ class Settings: ...@@ -85,13 +87,23 @@ class Settings:
'default_cfg_file': default_cfg_file, 'default_cfg_file': default_cfg_file,
} }
self.__args = {} self.__args = {}
self.__set_cli_args(cli_args)
def set_cli_args(self, cli_args: Dict[str, any]): def __set_cli_args(self, cli_args: Dict[str, any]):
self.__args = ({k: v for k, v in cli_args.items() if v}) self.__args = ({k: v for k, v in cli_args.items() if v})
self.__settings.update(self.__args) self.__settings.update(self.__args)
def update(self, settings: Dict[str, any]): def update(self, cfg_file: str):
self.__settings.update(settings) try:
with open(cfg_file, 'rb') as f:
self.__settings.update(toml_load(f))
except FileNotFoundError as e:
print(f'ERROR: Data file {cfg_file} not found! ({e})')
exit(1)
except TOMLDecodeError as e:
print(f'ERROR: Data file {cfg_file} is not in valid toml format! ({e})')
exit(2)
self.__settings.update(self.__args) self.__settings.update(self.__args)
@property @property
...@@ -143,9 +155,6 @@ class Settings: ...@@ -143,9 +155,6 @@ class Settings:
return self.__settings['default_cfg_file'] return self.__settings['default_cfg_file']
SETTINGS = Settings()
class SoftwareImage: class SoftwareImage:
def __init__(self, image: str, version: str, md5: str, size: int,): def __init__(self, image: str, version: str, md5: str, size: int,):
self.image: str = image self.image: str = image
...@@ -191,9 +200,6 @@ class ErrorCodes: ...@@ -191,9 +200,6 @@ class ErrorCodes:
return self.__readable.get(error_code, f'unknown: {error_code}') return self.__readable.get(error_code, f'unknown: {error_code}')
ERROR = ErrorCodes()
class PnpFlow: class PnpFlow:
__readable = { __readable = {
0: 'None', 0: 'None',
...@@ -232,9 +238,6 @@ class PnpFlow: ...@@ -232,9 +238,6 @@ class PnpFlow:
return self.__readable.get(state, 'unknown') return self.__readable.get(state, 'unknown')
PNPFLOW = PnpFlow()
class Device: class Device:
def __init__(self, udi: str, platform: str, hw_rev: str, serial: str, first_seen: str, last_contact: str, def __init__(self, udi: str, platform: str, hw_rev: str, serial: str, first_seen: str, last_contact: str,
src_address: str, current_job: str): src_address: str, current_job: str):
...@@ -317,17 +320,25 @@ class Device: ...@@ -317,17 +320,25 @@ class Device:
self.__status_class = status_class self.__status_class = status_class
app = Flask(__name__, template_folder='./templates') class Images:
if SETTINGS.debug: def __init__(self, images_file):
app.debug = True self.__images = {}
else: self.load_image_data(images_file)
# disable FLASK console output
logging.getLogger("werkzeug").disabled = True
cli.show_server_banner = lambda *args: None
def load_image_data(self, images_file):
try:
with open(images_file, 'rb') as f:
self.__images = toml_load(f)
except FileNotFoundError as e:
print(f'ERROR: Data file {images_file} not found! ({e})')
exit(1)
except TOMLDecodeError as e:
print(f'ERROR: Data file {images_file} is not in valid toml format! ({e})')
exit(2)
current_dir = Path(__file__) @property
devices: Dict[str, Device] = {} def images(self) -> Dict[str, any]:
return self.__images
def configure_logger(path): def configure_logger(path):
...@@ -369,33 +380,8 @@ def log_critical(message): ...@@ -369,33 +380,8 @@ def log_critical(message):
log.critical(message) log.critical(message)
def load_data(): def parse_arguments() -> arg_Namespace:
global SETTINGS parser = ArgumentParser(
global IMAGES
try:
with open(SETTINGS.cfg_file, 'rb') as f:
SETTINGS.update(toml_load(f))
except FileNotFoundError as e:
print(f'ERROR: Data file {SETTINGS.cfg_file} not found! ({e})')
exit(1)
except TOMLDecodeError as e:
print(f'ERROR: Data file {SETTINGS.cfg_file} is not valid toml! ({e})')
exit(2)
try:
with open(SETTINGS.image_data, 'rb') as f:
IMAGES = toml_load(f)
except FileNotFoundError as e:
print(f'ERROR: Data file {SETTINGS.image_data} not found! ({e})')
exit(1)
except TOMLDecodeError as e:
print(f'ERROR: Data file {SETTINGS.image_data} is not valid toml! ({e})')
exit(2)
def parse_arguments() -> argparse.Namespace:
parser = argparse.ArgumentParser(
prog='open-pnp.py', prog='open-pnp.py',
description='This is a basic implementation of the Cisco PnP protocol. It is intended to roll out image updates' description='This is a basic implementation of the Cisco PnP protocol. It is intended to roll out image updates'
' and configurations for Cisco IOS/IOS-XE devices on day0.', ' and configurations for Cisco IOS/IOS-XE devices on day0.',
...@@ -540,11 +526,9 @@ def pnp_bye(udi: str, correlator: str) -> str: ...@@ -540,11 +526,9 @@ def pnp_bye(udi: str, correlator: str) -> str:
return _template return _template
SERIAL_NUM_RE = re_compile(r'PID:(?P<product_id>\w+(?:-\w+)*),VID:(?P<hw_version>\w+),SN:(?P<serial_number>\w+)')
def create_new_device(udi: str, src_add: str): def create_new_device(udi: str, src_add: str):
platform, hw_rev, serial = SERIAL_NUM_RE.findall(udi)[0] serial_num_re = re_compile(r'PID:(?P<product_id>\w+(?:-\w+)*),VID:(?P<hw_version>\w+),SN:(?P<serial_number>\w+)')
platform, hw_rev, serial = serial_num_re.findall(udi)[0]
devices[udi] = Device( devices[udi] = Device(
udi=udi, udi=udi,
first_seen=strftime(SETTINGS.time_format), first_seen=strftime(SETTINGS.time_format),
...@@ -557,7 +541,7 @@ def create_new_device(udi: str, src_add: str): ...@@ -557,7 +541,7 @@ def create_new_device(udi: str, src_add: str):
) )
device = devices[udi] device = devices[udi]
device.backoff = True device.backoff = True
for image, image_data in IMAGES.items(): for image, image_data in IMAGES.images.items():
if platform in image_data['models']: if platform in image_data['models']:
device.target_image = SoftwareImage( device.target_image = SoftwareImage(
image=image, image=image,
...@@ -612,6 +596,10 @@ def get_local_ip_addresses() -> List[str]: ...@@ -612,6 +596,10 @@ def get_local_ip_addresses() -> List[str]:
return _addresses return _addresses
# flask
app = Flask(__name__, template_folder='./templates')
@app.route('/') @app.route('/')
def root(): def root():
return redirect('/status', 302) return redirect('/status', 302)
...@@ -639,7 +627,8 @@ def buttons(): ...@@ -639,7 +627,8 @@ def buttons():
button = list(request.form.values())[0] button = list(request.form.values())[0]
if button == 'Reload CFG': if button == 'Reload CFG':
load_data() IMAGES.load_image_data(SETTINGS.image_data)
SETTINGS.update(SETTINGS.cfg_file)
if udi in devices.keys(): if udi in devices.keys():
device = devices[udi] device = devices[udi]
...@@ -777,8 +766,20 @@ def pnp_work_response(): ...@@ -777,8 +766,20 @@ def pnp_work_response():
if __name__ == '__main__': if __name__ == '__main__':
SETTINGS.set_cli_args(vars(parse_arguments())) ERROR = ErrorCodes()
load_data() PNPFLOW = PnpFlow()
SETTINGS = Settings(vars(parse_arguments()))
SETTINGS.update(SETTINGS.cfg_file)
IMAGES = Images(SETTINGS.image_data)
devices: Dict[str, Device] = {}
if SETTINGS.debug:
app.debug = True
else:
# disable FLASK console output
logging.getLogger("werkzeug").disabled = True
cli.show_server_banner = lambda *args: None
if SETTINGS.image_url == '': if SETTINGS.image_url == '':
print(f'image_url not set, check {SETTINGS.cfg_file}') print(f'image_url not set, check {SETTINGS.cfg_file}')
......
...@@ -66,9 +66,12 @@ ...@@ -66,9 +66,12 @@
h1 { h1 {
text-align: center; text-align: center;
} }
input { input.reload_cfg {
width: 100px; width: 100px;
} }
input.action {
width: 70px;
}
div.header { div.header {
width: 100%; width: 100%;
padding: 5px; padding: 5px;
...@@ -163,11 +166,11 @@ ...@@ -163,11 +166,11 @@
<td> <td>
<!-- <!--
<form method="post" action="/buttons"> <form method="post" action="/buttons">
<input type="submit" value="Refresh" name="{{ device.udi }}" {{ device.refresh_button }}""/> <input class="action" type="submit" value="Refresh" name="{{ device.udi }}" {{ device.refresh_button }}""/>
</form> </form>
--> -->
<form method="post" action="/buttons"> <form method="post" action="/buttons">
<input type="submit" value="Remove" name="{{ device.udi }}"/> <input class="action" type="submit" value="Remove" name="{{ device.udi }}"/>
</form> </form>
</td> </td>
{% if debug %} {% if debug %}
...@@ -215,7 +218,7 @@ ...@@ -215,7 +218,7 @@
</footer> </footer>
<div class="reload_cfg"> <div class="reload_cfg">
<form class="reload_cfg" method="post" action="/buttons"> <form class="reload_cfg" method="post" action="/buttons">
<input type="submit" value="Reload CFG" name="reload_data"/> <input class="reload_cfg" type="submit" value="Reload CFG" name="reload_data"/>
</form> </form>
</div> </div>
</div> </div>
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment