Initial import from bf-network-r6.opencode — opencode migration
This commit is contained in:
29
docs/nas-vlan20/nautobot-image/Dockerfile
Normal file
29
docs/nas-vlan20/nautobot-image/Dockerfile
Normal file
@@ -0,0 +1,29 @@
|
||||
# Nautobot + Golden Config + Nornir, extending the official image in place
|
||||
# (not a satellite/side image). Reconstructed 2026-07-22 from the running
|
||||
# container's actual `docker image history` after the original build context
|
||||
# (referenced in git history as "scratch/nautobot-gc-build") turned out to
|
||||
# never have been committed to this repo -- this file replaces that dangling
|
||||
# reference as the real, versioned source of truth going forward.
|
||||
#
|
||||
# Rebuild: docker build -t bf-nautobot-golden-config:<tag> docs/nas-vlan20/nautobot-image/
|
||||
# then point docs/nas-vlan20/stacks/210-nautobot.yml's 3 `image:` lines at <tag>
|
||||
# and redeploy the Portainer stack (id 203, endpoint 14).
|
||||
# Rollback: point those 3 image lines back at ghcr.io/nautobot/nautobot:3.1.7
|
||||
# (drops the golden-config/nornir plugins; Nautobot core itself is unmodified).
|
||||
FROM ghcr.io/nautobot/nautobot:3.1.7
|
||||
|
||||
USER root
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends procps && rm -rf /var/lib/apt/lists/*
|
||||
RUN pip install --no-cache-dir \
|
||||
nautobot-golden-config \
|
||||
nautobot-plugin-nornir \
|
||||
nornir-netmiko \
|
||||
nornir-napalm \
|
||||
napalm-ros \
|
||||
librouteros \
|
||||
"setuptools<81"
|
||||
|
||||
COPY --chown=nautobot:nautobot nautobot_config.py /opt/nautobot/nautobot_config.py
|
||||
COPY --chown=nautobot:nautobot allnet_sg8316m_driver.py /opt/nautobot/allnet_sg8316m_driver.py
|
||||
|
||||
USER nautobot
|
||||
89
docs/nas-vlan20/nautobot-image/allnet_sg8316m_driver.py
Normal file
89
docs/nas-vlan20/nautobot-image/allnet_sg8316m_driver.py
Normal file
@@ -0,0 +1,89 @@
|
||||
"""Custom Netmiko driver for the ALL-SG8316M (sw1) -- Telnet-only, IOS-like but
|
||||
non-standard CLI (no `terminal length`, `--More--` paging, `end` from
|
||||
config-vlan skips straight to the top level). No stock Netmiko/NAPALM driver
|
||||
matches this device.
|
||||
|
||||
NOT YET WIRED IN: this module is imported nowhere yet. The `claudecode`
|
||||
Telnet account on sw1 is currently rejected ("Incorrect User Name!!", found
|
||||
2026-07-16) so this driver is unverified against the real device. Once
|
||||
working credentials exist, activate it from nautobot_config.py:
|
||||
|
||||
import importlib
|
||||
import allnet_sg8316m_driver # noqa: F401 (registers itself on import)
|
||||
|
||||
and add to PLUGINS_CONFIG["nautobot_plugin_nornir"]["dispatcher_mapping"]:
|
||||
|
||||
"allnet_sg8316m": "allnet_sg8316m_driver.GoldenConfigAllnetSg8316m"
|
||||
|
||||
(dispatcher class still needs writing once the connection class below is
|
||||
confirmed to log in cleanly -- see project_nautobot_allnet_cudy_scope_20260716
|
||||
memory for the full status and the two netmiko gotchas that cost the most
|
||||
time: device_type must contain the literal substring "_telnet" for Netmiko to
|
||||
pick the Telnet transport at all (BaseConnection.__init__ checks this by
|
||||
string, not by class hierarchy), and `netmiko.ssh_dispatcher` at package level
|
||||
is the dispatcher *function*, not the submodule -- reach CLASS_MAPPER via
|
||||
`importlib.import_module("netmiko.ssh_dispatcher")`.
|
||||
"""
|
||||
|
||||
import importlib
|
||||
import time
|
||||
|
||||
from netmiko.cisco_base_connection import CiscoBaseConnection
|
||||
|
||||
_nm_dispatch = importlib.import_module("netmiko.ssh_dispatcher")
|
||||
|
||||
|
||||
class AllnetSg8316mTelnet(CiscoBaseConnection):
|
||||
def session_preparation(self):
|
||||
self._test_channel_read(pattern=r"[>#]")
|
||||
self.set_base_prompt()
|
||||
self.ansi_escape_codes = True
|
||||
|
||||
def disable_paging(self, *args, **kwargs):
|
||||
# No config knob for this on this firmware; paging is handled
|
||||
# per-command in send_command_paged() below instead.
|
||||
return ""
|
||||
|
||||
def telnet_login(
|
||||
self,
|
||||
pri_prompt_terminator=r"#\s*$",
|
||||
alt_prompt_terminator=r">\s*$",
|
||||
username_pattern=r"Username",
|
||||
pwd_pattern=r"Password",
|
||||
delay_factor=1,
|
||||
max_loops=20,
|
||||
):
|
||||
return super().telnet_login(
|
||||
pri_prompt_terminator=pri_prompt_terminator,
|
||||
alt_prompt_terminator=alt_prompt_terminator,
|
||||
username_pattern=username_pattern,
|
||||
pwd_pattern=pwd_pattern,
|
||||
delay_factor=delay_factor,
|
||||
max_loops=max_loops,
|
||||
)
|
||||
|
||||
def send_command_paged(self, command_string, max_pages=200, delay=0.3):
|
||||
"""sg8316m paginates with '--More--' and has no length-0 setting;
|
||||
page through by sending a space each time it appears."""
|
||||
self.write_channel(command_string + self.RETURN)
|
||||
time.sleep(delay)
|
||||
output = ""
|
||||
for _ in range(max_pages):
|
||||
chunk = self.read_channel()
|
||||
output += chunk
|
||||
if "--More--" in chunk:
|
||||
self.write_channel(" ")
|
||||
time.sleep(delay)
|
||||
continue
|
||||
if any(p in chunk for p in ["sw1#", "sw1>"]) and "--More--" not in chunk:
|
||||
break
|
||||
if chunk == "":
|
||||
time.sleep(delay)
|
||||
return output.replace("--More--", "")
|
||||
|
||||
|
||||
# Registered under the "_telnet" suffix required by Netmiko's own transport
|
||||
# selection (see module docstring) -- registration is safe/inert on its own,
|
||||
# nothing calls ConnectHandler(device_type="allnet_sg8316m_telnet") yet.
|
||||
_nm_dispatch.CLASS_MAPPER["allnet_sg8316m_telnet"] = AllnetSg8316mTelnet
|
||||
_nm_dispatch.platforms = list(_nm_dispatch.CLASS_MAPPER.keys())
|
||||
91
docs/nas-vlan20/nautobot-image/nautobot_config.py
Normal file
91
docs/nas-vlan20/nautobot-image/nautobot_config.py
Normal file
@@ -0,0 +1,91 @@
|
||||
import os
|
||||
import sys
|
||||
|
||||
from nautobot.core.settings import * # noqa F401,F403
|
||||
from nautobot.core.settings_funcs import is_truthy, parse_redis_connection
|
||||
|
||||
if DATABASES["default"]["ENGINE"].endswith("mysql"):
|
||||
DATABASES["default"]["OPTIONS"] = {"charset": "utf8mb4"}
|
||||
|
||||
SECRET_KEY = os.getenv("NAUTOBOT_SECRET_KEY", "05^)=zgn3ytglmp_iv4@era^e23u3#_wtop-@yeaeazempdy^=")
|
||||
|
||||
INSTALLATION_METRICS_ENABLED = is_truthy(os.getenv("NAUTOBOT_INSTALLATION_METRICS_ENABLED", "True"))
|
||||
|
||||
# --- BF-Netzwerk additions: Golden Config + Nornir (TP21) ---
|
||||
#
|
||||
# Extends the stock config shipped in the base ghcr.io/nautobot/nautobot image
|
||||
# (unchanged above this point) instead of replacing it, so a rollback is just
|
||||
# "point the compose file's image tag back at ghcr.io/nautobot/nautobot:3.1.7".
|
||||
PLUGINS = [
|
||||
"nautobot_plugin_nornir",
|
||||
"nautobot_golden_config",
|
||||
]
|
||||
|
||||
PLUGINS_CONFIG = {
|
||||
"nautobot_plugin_nornir": {
|
||||
"nornir_settings": {
|
||||
# Per-device creds via each Device's SecretsGroup (ap1/ap2 -> "RouterOS
|
||||
# Nornir" group, access_type=Generic to match this class's default --
|
||||
# see get_device_creds()/_get_access_type_value() in nautobot_secrets.py).
|
||||
# Switched 2026-07-22 from env_vars.CredentialsEnvVars (a single global
|
||||
# NAPALM_USERNAME/PASSWORD pair for the whole fleet) so future devices
|
||||
# (CRS326 etc.) can carry their own credentials.
|
||||
"credentials": "nautobot_plugin_nornir.plugins.credentials.nautobot_secrets.CredentialsNautobotSecrets",
|
||||
"runner": {"plugin": "threaded", "options": {"num_workers": 5}},
|
||||
},
|
||||
# nautobot-plugin-nornir 2.0+ dropped PLUGINS_CONFIG["dispatcher_mapping"]
|
||||
# (it now errors on boot if present) -- per-platform dispatcher overrides
|
||||
# moved onto the Platform model itself (network_driver_mappings). The
|
||||
# ALLNET sg8316m custom driver will be wired in there once credentials
|
||||
# are sorted, not here.
|
||||
},
|
||||
"nautobot_golden_config": {
|
||||
# Backup + Intended-Config + Compliance enabled 2026-07-22 (TP21 follow-up
|
||||
# session) once a Jinja template + Git repo target existed.
|
||||
#
|
||||
# Plan + Deploy enabled 2026-08-13 (TP22 Schritt 3), after backup scrubbing
|
||||
# (ConfigReplace) and compliance were green on all four RouterOS devices.
|
||||
# Two binding governance rules apply -- see docs/teilprojekte.md TP22:
|
||||
# 1. Config Plans ONLY feature-/manual-scoped, never "full intended":
|
||||
# a full push would write the literal `passphrase="<<REDACTED>>"` to the
|
||||
# APs (kicking every WLAN client) and `private-key="<<REDACTED>>"` to the
|
||||
# core (killing the WireGuard hub). Sections containing the mask are
|
||||
# excluded from any push.
|
||||
# 2. The AP template is never pushed line-by-line to a live device: it
|
||||
# transiently sets vlan-filtering=no, which severs the vlan99 management
|
||||
# path mid-session. Full re-provisioning goes via file upload + /import.
|
||||
#
|
||||
# enable_postprocessing stays OFF on purpose: render_secrets would not fix the
|
||||
# compliance side (the stored intended would keep the un-rendered Jinja
|
||||
# expression -> permanent false positive) and pushing PSKs has no use here.
|
||||
"enable_backup": True,
|
||||
"enable_compliance": True,
|
||||
"enable_intended": True,
|
||||
"enable_sotagg": True,
|
||||
"enable_plan": True,
|
||||
"enable_deploy": True,
|
||||
"enable_postprocessing": False,
|
||||
# REQUIRED for deploy on RouterOS (found the hard way, 2026-08-13):
|
||||
# the default dispatcher resolves to NAPALM because Platform.network_driver_mappings
|
||||
# carries `napalm: ros` -- but napalm-ros is a READ-ONLY driver. Backup (get_config)
|
||||
# works, merge_config does not and fails with an empty error:
|
||||
# NornirNautobotException: E1015: The method merge_config failed ... ``
|
||||
# nornir-nautobot ships a RouterOS-specific Netmiko dispatcher whose merge_config
|
||||
# pushes via netmiko_send_config -- pin it explicitly. Key = network_driver name.
|
||||
"custom_dispatcher": {
|
||||
"mikrotik_routeros": "nornir_nautobot.plugins.tasks.dispatcher.mikrotik_routeros.NetmikoMikrotikRouteros",
|
||||
},
|
||||
"postprocessing_callables": [],
|
||||
"postprocessing_subscribed": [],
|
||||
"per_feature_bar": True,
|
||||
"per_feature_width": 0.13,
|
||||
"per_feature_height": 4,
|
||||
"default_deploy_status": "Not Approved",
|
||||
"get_custom_compliance": None,
|
||||
"sot_agg_transposer": None,
|
||||
},
|
||||
}
|
||||
|
||||
NAPALM_USERNAME = os.getenv("NAUTOBOT_NAPALM_USERNAME", "")
|
||||
NAPALM_PASSWORD = os.getenv("NAUTOBOT_NAPALM_PASSWORD", "")
|
||||
NAPALM_TIMEOUT = int(os.getenv("NAUTOBOT_NAPALM_TIMEOUT", "30"))
|
||||
Reference in New Issue
Block a user