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 ea41b546 authored by thl-cmk's avatar thl-cmk :flag_na:
Browse files

reorg of data files

reorg platforms.py and images.py in images.json
reorg settings.py in open-pnp.ini
remove imports for platforms.py/images.py/settings.py
add reload data function
parent 54196217
No related branches found
No related tags found
No related merge requests found
2023-01-26: reorg platforms.py and images.py in images.json
reorg settings.py in open-pnp.ini
remove imports for platforms.py/images.py/settings.py
add reload data function
\ No newline at end of file
......@@ -18,16 +18,19 @@ ASR routers and so on.
## How to use
---
### IOS-XE Images
Place the IOS-XE images on an HTTP server where the new devices can download them. If you use this PnP server to provide the `images` place them in the images' subdirectory. For the images I would recommend using a _real_ HTTP server.
---
### Configuration files
Create for each device a configuration file named SERIALNUMBER.cfg. i.e.: [`FCZ094210DS.cfg`](pnp/configs/FGL223590FL.cfg). Place the configuration files also on your HTTP server, so the new devices can download them. In cas the PnP server should deliver the configuration files, copy them in the `configs` subdirectory.
Create for each device a configuration file named SERIALNUMBER.cfg. i.e.: [`FCZ094210DS.cfg`](pnp/configs/FGL223590FL.cfg). Place the configuration files also on your HTTP server, so the new devices can download them. In case the PnP server should deliver the configuration files, copy them in the `configs` subdirectory.
**Hint**: you can use different HTTP servers for the images and the configuration files
**Note**: the PnP server runs on HTTP. So there is no encryption for the configuration files as the are downloaded by the new devices.
---
### Install the PnP server:
on Linux
......@@ -67,10 +70,12 @@ c:\>git clone https://thl-cmk.hopto.org/gitlab/bits-and-bytes/cisco_day0_provisi
c:\>cd cisco_day0_provision\pnp
c:\cisco_day0_provision\pnp>python -m venv .venv
c:\cisco_day0_provision\pnp>.venv\Scripts\activate.bat
(.venv)c:\cisco_day0_provision\pnp>pip install flask
(.venv)c:\cisco_day0_provision\pnp>pip install xmltodict
(.venv)c:\cisco_day0_provision\pnp>pip install requests
(.venv)c:\cisco_day0_provision\pnp>pip install python-dotenv
(.venv)c:\cisco_day0_provision\pnp>python open-pnp.py
Running PnP server. Stop with ctrl+c
......@@ -87,23 +92,22 @@ You can check if the PnP server is running by opening a web browser and accessin
![PnP server new status page](pnp-empty-status.png)
---
### Configure the PnP server
to use the PnP server you need to configure the server by modifying the
- [**_settings.py_**](/pnp/vars/settings.py)
- [**_images.py_**](/pnp/vars/images.py)
- [**_platforms.py_**](/pnp/vars/platforms.py)
to use the PnP server you need to configure the server by modifying the following files
files in the `vars` subdirectory.
- [**_settings.py_**](/pnp/open-pnp.ini)
- [**_images.py_**](/pnp/images.json)
**Note**: after changing the PnP server configuration you need to restart the PnP server.
**NOTE**: after changing the PnP server configuration you need to restart the PnP server.
#### Global variables in [**_settings.py_**](/pnp/vars/settings.py)
---
#### Global variables in [**open-pnp.ini_**](/pnp/open-pnp.ini)
```
# BIND_PNP_SERVER = ::'
[settings]
# BIND_PNP_SERVER = '0.0.0.0'
# PORT = 8080
# TIME_FORMAT = '%Y-%m-%dT%H:%M:%S'
# STATUS_REFRESH = 60
......@@ -112,50 +116,46 @@ files in the `vars` subdirectory.
# LOG_FILE = 'log/pnp_debug.log'
IMAGE_BASE_URL = 'http://192.168.10.133:8080/images'
CONFIG_BASE_URL = 'http://192.168.10.133:8080/configs'
# IMAGE_DATA = 'images.json'
```
- **BIND_PNP_SERVER**: the IP-address of your python box
- **BIND_PNP_SERVER**: the IP-address of your open-pnp server box. (Use `'::'` for IPv6)
- **PORT**: the TCP port the server should listen on (remember for port 80 the server needs to run as root)
- **TIME_FORMAT**: the time format used in the status page
- **STATUS_REFRESH**: the interval in seconds the status page will automatically reload
- **DEBUG**: enable debug output with `DEBUG=True`
- **DEBUG**: enable debug output with `DEBUG = True`. Can be `True` or `False`.
- **LOG_FILE**: path/name of the log file
- **LOG_TO_FILE**: write log output to file
- **IMAGE_BASE_URL**: the base URL for your images (without `/` at the end)
- **CONFIG_BASE_URL**: the base URL for your configuration files (without `/` at the end)
- **LOG_TO_FILE**: write log output to file. Can be `True` or `False`.
- **IMAGE_BASE_URL**: the base URL for your images
- **CONFIG_BASE_URL**: the base URL for your configuration files
- **IMAGE_DATA**: the file containing the data of your IOS/IOS-XE images
**Note**: you need to uncomment (remove `# `) the lines if you change the values.
#### _IMAGES_ dictionary in [**_images.py_**](/pnp/vars/images.py)
---
#### _IMAGES_ dictionary in [**_images.json_**](/images.json)
Each entry in the _IMAGES_ dictionary contains
- a unique name, i.e., the product family + version
- the name of the IOS-XE image file
- the IOS-XE version of the image
- the md5 sum of the IOS-XE image file
- the size of the IOS-XE image file in bytes
- the **name** of the image file as key
- the IOS/IOS-XE **version** of the image
- the **md5** checksum of the image file
- the **size** of the image file in bytes
- a list of **models** where that image should be used
```
'C1100_17_06_04': SoftwareImage(
image='c1100-universalk9.17.06.04.SPA.bin',
version='17.06.04',
md5_image='2caa962f5ed0ecc52f99b90c733c54de',
size=651402492,
)
"c1000-universalk9-mz.152-7.E7.bin": {
"version": "15.2(7)E7",
"md5": "1e6f508499c36434f7035b83a4018390",
"size": 16499712,
"models": ["C1000-8T-2G-L", "C1000-24P-4G-L", "C1000-24T-4G-L", "C1000-24T-4X-L", "C1000-48P-4G-L", "C1000-48T-4X-L"]
}
```
#### _PLATFORMS_ dictionary in [**_platforms.py_**](/pnp/vars/platforms.py)
**NOTE:** By default _open-pnp_ expects the image data in _images.json_. You can change this with the key _IMAGE_DATA_ in _settings.ini_.
Each entry in the _PLATFORMS_ dictionary contains
- a unique name that exactly matches the model name (PID) of the device
- a pointer to the image for this model in the _IMAGES_ dictionary
```
'C1117-4PMLTEEAWE': Model(
image='C1100_17_06_04',
)
```
**NOTE:** this file needs to be in valid json format.
---
### PnP server discovery
The IOS-XE device can discover a PnP server via DHCP option 43 or using DNS lookup for the hostname _pnpserver.your.domain_. Replaced _your.domain_ by the DNS domain the device receives via DHCP. With DHCP, the DHCP server needs to send the vendor option 43.
......@@ -180,6 +180,7 @@ ip dhcp pool autoinstall
```
For more details on PnP server discovery options see [PnP server discovery](https://developer.cisco.com/site/open-plug-n-play/learn/learn-open-pnp-protocol/). There you will also find an overview how the PnP protocol works.
---
### PnP Status page
You can monitor the PnP progress on the PnP server status page.
......
{
"your_image_name.bin": {
"version": "0.0.0",
"md5": "fdb9c92bae37f9130d0ee6761afe2919",
"size": 9999999999,
"models": []
},
"cat9k_iosxe.17.06.01.SPA.bin": {
"version": "17.6.1",
"md5": "fdb9c92bae37f9130d0ee6761afe2919",
"size": 1,
"models": ["C9500-24Q", "C9300-24P"]
},
"asr1000-universalk9.17.05.01a.SPA.bin": {
"version": "17.5.1a",
"md5": "0e4b1fc1448f8ee289634a41f75dc215",
"size": 1,
"models": ["ASR1001-HX"]
},
"c1000-universalk9-mz.152-7.E7.bin": {
"version": "15.2(7)E7",
"md5": "1e6f508499c36434f7035b83a4018390",
"size": 16499712,
"models": ["C1000-8T-2G-L", "C1000-24P-4G-L", "C1000-24T-4G-L", "C1000-24T-4X-L", "C1000-48P-4G-L", "C1000-48T-4X-L"]
},
"c1100-universalk9.16.12.01a.SPA.bin": {
"version": "16.12.1a",
"md5": "045d73625025b4f77c65c7800b7faa2b",
"size": 541469788,
"models": []
},
"c1100-universalk9.17.01.01.SPA.bin": {
"version": "17.1.1",
"md5": "62e79c54994b82fc862c2ca043dcd543",
"size": 573996288,
"models": []
},
"c1100-universalk9.17.02.03.SPA.bin": {
"version": "17.2.3",
"md5": "4986d253b333d21b1b80c76f6d2267ca",
"size": 589675224,
"models": []
},
"c1100-universalk9.17.03.05.SPA.bin": {
"version": "17.3.5",
"md5": "64aa0df0806f7f962d66d325ff917e4a",
"size": 604409868,
"models": []
},
"c1100-universalk9.17.04.02.SPA.bin": {
"version": "17.4.2",
"md5": "40b7ec81c4e4cdc8b3683a3938ae8361",
"size": 651402492,
"models": []
},
"c1100-universalk9.17.05.01a.SPA.bin": {
"version": "17.5.1a",
"md5": "85d86916e33d27ae9867eec822206b97",
"size": 674793716,
"models": []
},
"c1100-universalk9.17.06.03a.SPA.bin": {
"version": "17.6.3a",
"md5": "2501b21b6fa3f71ea6acd7d59bcc8423",
"size": 706422748,
"models": []
},
"c1100-universalk9.17.06.04.SPA.bin": {
"version": "17.6.4",
"md5": "2caa962f5ed0ecc52f99b90c733c54de",
"size": 706565772,
"models": ["C1117-4PMLTEEAWE"]
},
"c1100-universalk9.17.07.02.SPA.bin": {
"version": "17.7.2",
"md5": "b824743e09cfa2644ccb442ef3e48cd2",
"size": 727305300,
"models": []
},
"c1100-universalk9.17.08.01a.SPA.bin": {
"version": "17.8.1a",
"md5": "8997c56cb03b5dcb08f12bf82fe23988",
"size": 758605624,
"models": []
},
"c1100-universalk9.17.09.01a.SPA.bin": {
"version": "17.9.1a",
"md5": "b3efb230d869fa6e77a98b4130c89585",
"size": 684976080,
"models": []
},
"c3560cx-universalk9-mz.152-7.E7.bin": {
"version": "15.2(7)E7",
"md5": "1c4c0597d355a0926c3d7198c1167cae",
"size": 22967296,
"models": ["WS-C3560CX-12PD-S"]
}
}
\ No newline at end of file
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
[settings]
# BIND_PNP_SERVER = '0.0.0.0'
# BIND_PNP_SERVER = '::'
# PORT = 8080
......@@ -11,3 +9,4 @@ STATUS_REFRESH = 10
# LOG_FILE = 'log/pnp_debug.log'
IMAGE_BASE_URL = 'http://192.168.10.133:8080/images'
CONFIG_BASE_URL = 'http://192.168.10.133:8080/configs'
# IMAGE_DATA = 'images.json'
\ No newline at end of file
......@@ -20,17 +20,18 @@
# stop on import error for images.py and platforms.py
# removed extending path variable to ./vars
import re
from re import compile as re_compile
from flask import Flask, request, send_from_directory, render_template, Response, redirect, cli
from pathlib import Path
import xmltodict
from xmltodict import parse as xml_parse
from time import strftime
from typing import Optional, List, Dict, Any
import logging
from logging.handlers import RotatingFileHandler
from requests import head
# import importlib
# import sys
from json import load as json_load
from json.decoder import JSONDecodeError
from configparser import ConfigParser
try:
......@@ -40,53 +41,26 @@ except ModuleNotFoundError:
_netifaces = False
pass
BIND_PNP_SERVER = '0.0.0.0'
PORT = 8080
TIME_FORMAT = '%Y-%m-%dT%H:%M:%S'
STATUS_REFRESH = 60
DEBUG = False
LOG_TO_FILE = True
LOG_FILE = 'log/pnp_debug.log'
IMAGE_BASE_URL = ''
CONFIG_BASE_URL = ''
IMAGES = {}
PLATFORMS = {}
try:
from vars.settings import *
# (
# BIND_PNP_SERVER,
# PORT,
# TIME_FORMAT,
# STATUS_REFRESH,
# DEBUG,
# LOG_TO_FILE,
# LOG_FILE,
# IMAGE_BASE_URL,
# CONFIG_BASE_URL,
# )
except (ModuleNotFoundError, ImportError):
pass
try:
from vars.images import (
SoftwareImage,
IMAGES,
)
from vars.platforms import (
Model,
PLATFORMS,
)
except ModuleNotFoundError as e:
print(f'{e}')
exit(1)
CONFIG_BASE_URL = CONFIG_BASE_URL.rstrip('/')
IMAGE_BASE_URL = IMAGE_BASE_URL.rstrip('/')
# devine global variables
IMAGE_DATA = None # 'images.json'
BIND_PNP_SERVER = None # '0.0.0.0'
PORT = None # 8080
TIME_FORMAT = None # '%Y-%m-%dT%H:%M:%S'
STATUS_REFRESH = None # 60
DEBUG = None # False
LOG_TO_FILE = None # True
LOG_FILE = None # 'log/pnp_debug.log'
IMAGE_BASE_URL = None # ''
CONFIG_BASE_URL = None # ''
IMAGES = None # {}
class SoftwareImage:
def __init__(self, image: str, version: str, md5: str, size: int,):
self.image: str = image
self.version: str = version
self.md5: str = md5
self.size: int = size
class ErrorCodes:
__readable = {
......@@ -294,6 +268,48 @@ def log_critical(message):
log.critical(message)
def load_data():
global IMAGES
global IMAGE_DATA
global BIND_PNP_SERVER
global PORT
global TIME_FORMAT
global STATUS_REFRESH
global DEBUG
global LOG_TO_FILE
global LOG_FILE
global IMAGE_BASE_URL
global CONFIG_BASE_URL
config = ConfigParser()
config.read('open-pnp.ini')
IMAGE_DATA = config['settings'].get('IMAGE_DATA', 'images.json')
BIND_PNP_SERVER = config['settings'].get('BIND_PNP_SERVER', '0.0.0.0')
TIME_FORMAT = config['settings'].get('TIME_FORMAT', '%Y-%m-%dT%H:%M:%S')
LOG_FILE = config['settings'].get('LOG_FILE', 'log/pnp_debug.log')
IMAGE_BASE_URL = config['settings'].get('IMAGE_BASE_URL', '').rstrip('/')
CONFIG_BASE_URL = config['settings'].get('CONFIG_BASE_URL', '').rstrip('/')
#
PORT = config['settings'].get('PORT', '8080')
STATUS_REFRESH = config['settings'].get('STATUS_REFRESH', '60')
#
PORT = int(PORT) if PORT.isdigit() else 8080
STATUS_REFRESH = int(STATUS_REFRESH) if STATUS_REFRESH.isdigit() else 60
#
DEBUG = True if config['settings'].get('DEBUG', 'False') == 'True' else False
LOG_TO_FILE = True if config['settings'].get('LOG_TO_FILE', 'True') == 'True' else False
try:
with open(IMAGE_DATA, 'r') as f:
IMAGES = json_load(f)
except FileNotFoundError as e:
print(f'ERROR: Data file {IMAGE_DATA} not found! ({e})')
exit(1)
except JSONDecodeError as e:
print(f'ERROR: Data file {IMAGE_DATA} is not valid json! ({e})')
exit(2)
def pnp_device_info(udi: str, correlator: str, info_type: str) -> str:
# info_type can be one of:
# image, hardware, filesystem, udi, profile, all
......@@ -406,7 +422,7 @@ def pnp_bye(udi: str, correlator: str) -> str:
return _template
SERIAL_NUM_RE = re.compile(r'PID:(?P<product_id>\w+(?:-\w+)*),VID:(?P<hw_version>\w+),SN:(?P<serial_number>\w+)')
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):
......@@ -423,15 +439,16 @@ def create_new_device(udi: str, src_add: str):
)
device = devices[udi]
device.backoff = True
if device.platform in PLATFORMS:
platform = PLATFORMS[device.platform]
if platform.image in IMAGES:
device.target_image = IMAGES[platform.image]
else:
device.error_code = ERROR.ERROR_NO_IMAGE
device.hard_error = True
else:
device.error_code = ERROR.ERROR_NO_PLATFORM
for image, image_data in IMAGES.items():
if platform in image_data['models']:
device.target_image = SoftwareImage(
image=image,
version=image_data['version'],
md5=image_data['md5'],
size=image_data['size']
)
if not device.target_image:
device.error_code = ERROR.ERROR_NO_IMAGE
device.hard_error = True
......@@ -483,10 +500,6 @@ def get_local_ip_addresses() -> List[str]:
return _addresses
def reload_data():
pass
@app.route('/')
def root():
return redirect('/status', 302)
......@@ -516,7 +529,7 @@ def buttons():
button = list(request.form.values())[0]
if button == 'Reload':
reload_data()
load_data()
if udi in devices.keys():
device = devices[udi]
......@@ -547,7 +560,7 @@ def pnp_hello():
@app.route('/pnp/WORK-REQUEST', methods=['POST'])
def pnp_work_request():
src_add = request.environ.get('HTTP_X_REAL_IP', request.remote_addr)
data = xmltodict.parse(request.data)
data = xml_parse(request.data)
if DEBUG:
log_info(f'REQUEST: {data}')
correlator = data['pnp']['info']['@correlator']
......@@ -605,7 +618,7 @@ def pnp_work_request():
@app.route('/pnp/WORK-RESPONSE', methods=['POST'])
def pnp_work_response():
data = xmltodict.parse(request.data)
data = xml_parse(request.data)
if DEBUG:
log_info(f'RESPONSE: {data}')
src_add = request.environ.get('HTTP_X_REAL_IP', request.remote_addr)
......@@ -666,6 +679,8 @@ def pnp_work_response():
if __name__ == '__main__':
load_data()
if IMAGE_BASE_URL == '':
print('IMAGE_BASE_URL not set, check ./vars/vars.py')
exit(1)
......
......@@ -11,7 +11,6 @@
background-color: #d3cdcd;
padding: 5px;
}
/* .rotate { transform: rotate(-45deg); } */
td {
border: 1px solid #c5bfbf;
padding: 5px;
......@@ -33,35 +32,36 @@
</head>
<body>
<h1>PnP Day0 status page</h1>
<form method="post" action="/buttons">
<input type="submit" value="Reload" name="reload_data"/>
</form>
<!-- ToDo: find a nice place for the rload (data) button -->
<form method="post" action="/buttons">
<input type="submit" value="Reload" name="reload_data"/>
</form>
<table>
<tr>
<th><div class="rotate">Count</div></th>
<th><div class="rotate">Action</div></th>
<th><div class="rotate">Serial<br>number</div></th>
<th><div class="rotate">Platform</div></th>
<th><div class="rotate">HW<br>rev.</div></th>
<th><div class="rotate">IP-Address</div></th>
<th><div class="rotate">PnP<br>flow</div></th>
<th><div class="rotate">Status</div></th>
<th><div class="rotate">First<br>seen</div></th>
<th><div class="rotate">Last<br>contact</div></th>
<th><div class="rotate">Current<br>job</div></th>
<th><div class="rotate">Current<br>version</div></th>
<th><div class="rotate">Target<br>version</div></th>
<!-- <th><div class="rotate">Current<br>image</div></th>-->
<!-- <th><div class="rotate">Target<br>image</div></th>-->
<!-- <th><div class="rotate">Size</div></th>-->
<!-- <th><div class="rotate">Free space</div></th>-->
<!-- <th><div class="rotate">Destination</div></th>-->
<th><div class="rotate">Error<br>code</div></th>
<th><div class="rotate">Last<br>error</div></th>
<th><div class="rotate">Last<br>message</div></th>
<th><div class="rotate">Error<br>count</div></th>
<th><div class="rotate">Hard<br>error</div></th>
<!-- <th><div class="rotate">Backoff</div></th>-->
<th>Count</th>
<th>Action</th>
<th>Serial<br>number</th>
<th>Platform</th>
<th>HW<br>rev.</th>
<th>IP-Address</th>
<th>PnP<br>flow</th>
<th>Status</th>
<th>First<br>seen</th>
<th>Last<br>contact</th>
<th>Current<br>job</th>
<th>Current<br>version</th>
<th>Target<br>version</th>
<!-- <th>Current<br>image</th>-->
<!-- <th>Target<br>image</th>-->
<!-- <th>Size</th>-->
<!-- <th>Free space</th>-->
<!-- <th>Destination</th>-->
<th>Error<br>code</th>
<th>Last<br>error</th>
<th>Last<br>message</th>
<th>Error<br>count</th>
<th>Hard<br>error</th>
<!-- <th>Backoff</th>-->
</tr>
{% for device in devices %}
<tr>
......
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="refresh" content="{{ refresh }}">
<style>
body {background-color: rgb(141, 133, 133);}
table { border-spacing: 0; width: 100%}
tr:nth-child(even) { background-color: #D6EEEE; }
body {
background-color: rgb(141, 133, 133);
}
table {
border-spacing: 0;
width: 100%
}
tr:nth-child(even) {
background-color: #D6EEEE;
}
th {
border: 1px solid #2e2c2c;
background-color: #d3cdcd;
padding: 5px;
}
td {
border: 1px solid #c5bfbf;
padding: 5px;
}
td.error {
background-color: #dc143c;
}
td.warning {
background-color: #ffff00;
}
td.finished {
background-color: #7cfc00;
}
input { width: 70px; }
input {
width: 70px;
}
</style>
</head>
<body>
<h1>PnP Day0 status page</h1>
<table>
<tr>
<th><div class="rotate">Count</div></th>
<th>Action</th>
<th>Serial<br>number</th>
<th>Platform</th>
<th>HW<br>rev.</th>
<th>IP-Address</th>
<th>PnP<br>flow</th>
<th>Status</th>
<th>First<br>seen</th>
<th>Last<br>contact</th>
<th>Current<br>job</th>
<th>Current<br>version</th>
<th>Target<br>version</th>
<th>Current<br>image</th>
<th>Target<br>image</th>
<th>Size</th>
<th>Free<br>space</th>
<th>Destination</th>
<th>Error<br>code</th>
<th>Last<br>error</th>
<th>Last<br>message</th>
<th>Error<br>count</th>
<th>Hard<br>error</th>
<th>Backoff</th>
</tr>
{% for device in devices %}
<tr>
<td style="text-align: center;"> {{ loop.index }}</td>
<td>
<form method="post" action="/buttons">
<input type="submit" value="Refresh" name="{{ device.udi }}" {{ device.refresh_button }}""/>
</form>
<form method="post" action="/buttons">
<input type="submit" value="Remove" name="{{ device.udi }}"/>
</form>
</td>
<td class="{{ device.status}}"><a href="{{ config_base_url }}/{{ device.serial }}.cfg" target="_blank">{{ device.serial }}</a></td>
<td>{{ device.platform }}</td>
<td>{{ device.hw_rev }}</td>
<td>{{ device.ip_address }}</td>
<td>{{ device.pnp_flow_readable }}</td>
<td>{{ device.status }} </td>
<td>{{ device.first_seen }}</td>
<td>{{ device.last_contact }}</td>
<td>{{ device.current_job }}</td>
<td>{{ device.version }}</td>
<td>{{ device.target_image.version }}</td>
<td>{{ device.image }}</td>
<td> <a href="{{ image_base_url }}/{{ device.target_image.image }}" target="_blank">{{ device.target_image.image }}</a></td>
<td>{{ device.target_image.size }}</td>
<td>{{ device.destination_free }}</td>
<td>{{ device.destination_name }}</td>
<td>{{ device.error_code }}</td>
<td>{{ device.error_code_readable }}</td>
<td>{{ device.error_message }}</td>
<td>{{ device.error_count }}</td>
<td>{{ device.hard_error }}</td>
<td>{{ device.backoff }}</td>
</tr>
{% endfor %}
</table>
<footer>
<br><br>
<p>Written by: <a href="mailto:thl-cmk@outlook.com?subject=About your PnP server“,">thl-cmk</a> | Date: 2022-12-10 | for more information see
<a href="https://thl-cmk.hopto.org/gitlab/bits-and-bytes/cisco_day0_provision" target="_blank">Cisco Day0 provisioning</a> </p>
</footer>
<h1>PnP Day0 status page</h1>
<!-- ToDo: find a nice place for the rload (data) button -->
<form method="post" action="/buttons">
<input type="submit" value="Reload" name="reload_data" />
</form>
<table>
<tr>
<th>Count</th>
<th>Action</th>
<th>Serial<br>number</th>
<th>Platform</th>
<th>HW<br>rev.</th>
<th>IP-Address</th>
<th>PnP<br>flow</th>
<th>Status</th>
<th>First<br>seen</th>
<th>Last<br>contact</th>
<th>Current<br>job</th>
<th>Current<br>version</th>
<th>Target<br>version</th>
<th>Current<br>image</th>
<th>Target<br>image</th>
<th>Size</th>
<th>Free<br>space</th>
<th>Destination</th>
<th>Error<br>code</th>
<th>Last<br>error</th>
<th>Last<br>message</th>
<th>Error<br>count</th>
<th>Hard<br>error</th>
<th>Backoff</th>
</tr>
{% for device in devices %}
<tr>
<td style="text-align: center;"> {{ loop.index }}</td>
<td>
<!--
<form method="post" action="/buttons">
<input type="submit" value="Refresh" name="{{ device.udi }}" {{ device.refresh_button }}""/>
</form>
-->
<form method="post" action="/buttons">
<input type="submit" value="Remove" name="{{ device.udi }}" />
</form>
</td>
<td class="{{ device.status}}"><a href="{{ config_base_url }}/{{ device.serial }}.cfg" target="_blank">{{
device.serial }}</a></td>
<td>{{ device.platform }}</td>
<td>{{ device.hw_rev }}</td>
<td>{{ device.ip_address }}</td>
<td>{{ device.pnp_flow_readable }}</td>
<td>{{ device.status }} </td>
<td>{{ device.first_seen }}</td>
<td>{{ device.last_contact }}</td>
<td>{{ device.current_job }}</td>
<td>{{ device.version }}</td>
<td>{{ device.target_image.version }}</td>
<td>{{ device.image }}</td>
<td> <a href="{{ image_base_url }}/{{ device.target_image.image }}" target="_blank">{{
device.target_image.image }}</a></td>
<td>{{ device.target_image.size }}</td>
<td>{{ device.destination_free }}</td>
<td>{{ device.destination_name }}</td>
<td>{{ device.error_code }}</td>
<td>{{ device.error_code_readable }}</td>
<td>{{ device.error_message }}</td>
<td>{{ device.error_count }}</td>
<td>{{ device.hard_error }}</td>
<td>{{ device.backoff }}</td>
</tr>
{% endfor %}
</table>
<footer>
<br><br>
<p>Written by: <a href="mailto:thl-cmk@outlook.com?subject=About your PnP server“,">thl-cmk</a> | Date:
2022-12-10 | for more information see
<a href="https://thl-cmk.hopto.org/gitlab/bits-and-bytes/cisco_day0_provision" target="_blank">Cisco Day0
provisioning</a>
</p>
</footer>
</body>
</html>
</html>
\ No newline at end of file
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
class SoftwareImage:
def __init__(self, version: str, image: str, md5: str, size: int):
self.image: str = image
self.version: str = version
self.md5: str = md5
self.size: int = size
IMAGES = {
'dummy': SoftwareImage(
image='your_image_name.bin',
version='0.0.0',
md5='fdb9c92bae37f9130d0ee6761afe2919',
size=9999999999,
),
'CAT9K': SoftwareImage(
image='cat9k_iosxe.17.06.01.SPA.bin',
version='17.6.1',
md5='fdb9c92bae37f9130d0ee6761afe2919',
size=1,
),
'ASR1000': SoftwareImage(
image='asr1000-universalk9.17.05.01a.SPA.bin',
version='17.5.1a',
md5='0e4b1fc1448f8ee289634a41f75dc215',
size=1,
),
'C1000_15_02': SoftwareImage(
image='c1000-universalk9-mz.152-7.E7.bin',
version='15.2(7)E7',
md5='1e6f508499c36434f7035b83a4018390',
size=16499712,
),
'C1100_16_12': SoftwareImage(
image='c1100-universalk9.16.12.01a.SPA.bin',
version='16.12.1a',
md5='045d73625025b4f77c65c7800b7faa2b',
size=541469788,
),
'C1100_17_01': SoftwareImage(
image='c1100-universalk9.17.01.01.SPA.bin',
version='17.1.1',
md5='62e79c54994b82fc862c2ca043dcd543',
size=573996288,
),
'C1100_17_02': SoftwareImage(
image='c1100-universalk9.17.02.03.SPA.bin',
version='17.2.3',
md5='4986d253b333d21b1b80c76f6d2267ca',
size=589675224,
),
'C1100_17_03': SoftwareImage(
image='c1100-universalk9.17.03.05.SPA.bin',
version='17.3.5',
md5='64aa0df0806f7f962d66d325ff917e4a',
size=604409868,
),
'C1100_17_04': SoftwareImage(
image='c1100-universalk9.17.04.02.SPA.bin',
version='17.4.2',
md5='40b7ec81c4e4cdc8b3683a3938ae8361',
size=651402492,
),
'C1100_17_05': SoftwareImage(
image='c1100-universalk9.17.05.01a.SPA.bin',
version='17.5.1a',
md5='85d86916e33d27ae9867eec822206b97',
size=674793716,
),
'C1100_17_06_03': SoftwareImage(
image='c1100-universalk9.17.06.03a.SPA.bin',
version='17.6.3a',
md5='2501b21b6fa3f71ea6acd7d59bcc8423',
size=706422748,
),
'C1100_17_06_04': SoftwareImage(
image='c1100-universalk9.17.06.04.SPA.bin',
version='17.6.4',
md5='2caa962f5ed0ecc52f99b90c733c54de',
size=706565772,
),
'C1100_17_07': SoftwareImage(
image='c1100-universalk9.17.07.02.SPA.bin',
version='17.7.2',
md5='b824743e09cfa2644ccb442ef3e48cd2',
size=727305300,
),
'C1100_17_08': SoftwareImage(
image='c1100-universalk9.17.08.01a.SPA.bin',
version='17.8.1a',
md5='8997c56cb03b5dcb08f12bf82fe23988',
size=758605624,
),
'C1100_17_09': SoftwareImage(
image='c1100-universalk9.17.09.01a.SPA.bin',
version='17.9.1a',
md5='b3efb230d869fa6e77a98b4130c89585',
size=684976080,
),
'C3560CX': SoftwareImage(
image='c3560cx-universalk9-mz.152-7.E7.bin',
version='15.2(7)E7',
md5='1c4c0597d355a0926c3d7198c1167cae',
size=22967296,
),
}
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
class Model:
def __init__(self, image: str):
self.image: str = image
PLATFORMS = {
'C9300-24P': Model(
image='CAT9K',
),
'C9500-24Q': Model(
image='CAT9K',
),
'ASR1001-HX': Model(
image='ASR1000',
),
'C1117-4PMLTEEAWE': Model(
# image='C1100_16_12',
# image='C1100_17_09',
image='C1100_17_06_04',
),
'C1000-8T-2G-L': Model(
image='C1000_15_02',
),
'C1000-24P-4G-L': Model(
image='C1000_15_02',
),
'C1000-24T-4G-L': Model(
image='C1000_15_02',
),
'C1000-24T-4X-L': Model(
image='C1000_15_02',
),
'C1000-48P-4G-L': Model(
image='C1000_15_02',
),
'C1000-48T-4X-L': Model(
image='C1000_15_02',
),
'WS-C3560CX-12PD-S': Model(
image='C3560CX',
),
}
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