feat: set_hostname — update /etc/hostname, /etc/hosts, cert, Postfix
Implements set_hostname on ProxmoxSystemMixin: 1. Writes /etc/hostname (short name, base64-safe transfer) 2. Replaces old hostname in /etc/hosts via Python regex + base64 3. Updates /etc/mailname if present 4. Updates Postfix myhostname via postconf -e if installed 5. Applies hostname immediately at runtime via hostname(1) 6. Regenerates Proxmox node TLS certificate via pvecm updatecerts -f (falls back to pvenode cert create → pveproxy restart) Accepts bare hostname or FQDN. A reboot is required for the Proxmox node name to update in the web UI / cluster — the driver logs this. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
d1e6931ec5
commit
b1ba991e6d
@@ -0,0 +1,107 @@
|
||||
# Copyright 2025 The NetOrk Authors
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
"""LLDP-related NAPALM getters for Proxmox VE nodes."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
from typing import Any
|
||||
|
||||
|
||||
_JsonDict = dict[str, Any]
|
||||
|
||||
|
||||
class ProxmoxLLDPMixin:
|
||||
"""Mixin providing LLDP NAPALM methods."""
|
||||
|
||||
def get_lldp_neighbors(self) -> dict[str, list[_JsonDict]]:
|
||||
"""Return LLDP neighbours (requires lldpd on the Proxmox node)."""
|
||||
result: dict[str, list[_JsonDict]] = {}
|
||||
raw = self._exec_ssh_command(
|
||||
"lldpcli show neighbors summary 2>/dev/null || true"
|
||||
)
|
||||
current_iface = ""
|
||||
for line in raw.splitlines():
|
||||
m_iface = re.match(r"^\s*Interface:\s+(\S+?),?\s", line)
|
||||
if m_iface:
|
||||
current_iface = m_iface.group(1)
|
||||
result.setdefault(current_iface, [])
|
||||
continue
|
||||
m_sys = re.match(r"^\s*SysName:\s+(.+)", line)
|
||||
m_port = re.match(r"^\s*PortID:\s+\S+\s+(.+)", line)
|
||||
if m_sys and current_iface:
|
||||
hostname = m_sys.group(1).strip()
|
||||
if result[current_iface]:
|
||||
result[current_iface][-1]["hostname"] = hostname
|
||||
else:
|
||||
result[current_iface].append({"hostname": hostname, "port": ""})
|
||||
if m_port and current_iface and result[current_iface]:
|
||||
result[current_iface][-1]["port"] = m_port.group(1).strip()
|
||||
return result
|
||||
|
||||
def get_lldp_neighbors_detail(self, interface: str = "") -> dict[str, list[_JsonDict]]:
|
||||
"""Return detailed LLDP neighbour information."""
|
||||
result: dict[str, list[_JsonDict]] = {}
|
||||
raw = self._exec_ssh_command(
|
||||
"lldpcli show neighbors details 2>/dev/null || true"
|
||||
)
|
||||
current_iface = ""
|
||||
current_entry: _JsonDict = {}
|
||||
|
||||
def _flush():
|
||||
if current_iface and current_entry:
|
||||
result.setdefault(current_iface, []).append(current_entry.copy())
|
||||
|
||||
for line in raw.splitlines():
|
||||
m_iface = re.match(r"^\s*Interface:\s+(\S+?),?\s", line)
|
||||
if m_iface:
|
||||
_flush()
|
||||
current_iface = m_iface.group(1)
|
||||
if interface and current_iface != interface:
|
||||
current_iface = ""
|
||||
current_entry = {
|
||||
"parent_interface": "",
|
||||
"remote_chassis_id": "",
|
||||
"remote_system_name": "",
|
||||
"remote_port": "",
|
||||
"remote_port_description": "",
|
||||
"remote_system_description": "",
|
||||
"remote_system_capab": [],
|
||||
"remote_system_enable_capab": [],
|
||||
}
|
||||
continue
|
||||
if not current_iface:
|
||||
continue
|
||||
for key, pattern in (
|
||||
("remote_chassis_id", r"ChassisID:\s+\S+\s+(.+)"),
|
||||
("remote_system_name", r"SysName:\s+(.+)"),
|
||||
("remote_port", r"PortID:\s+\S+\s+(.+)"),
|
||||
("remote_port_description", r"PortDescr:\s+(.+)"),
|
||||
("remote_system_description", r"SysDescr:\s+(.+)"),
|
||||
):
|
||||
m = re.match(rf"^\s*{pattern}", line)
|
||||
if m:
|
||||
current_entry[key] = m.group(1).strip()
|
||||
|
||||
m_cap = re.match(r"^\s*Capability:\s+(\S+),\s+(\w+)", line)
|
||||
if m_cap:
|
||||
cap = m_cap.group(1).lower()
|
||||
enabled = m_cap.group(2).lower() == "on"
|
||||
current_entry["remote_system_capab"].append(cap)
|
||||
if enabled:
|
||||
current_entry["remote_system_enable_capab"].append(cap)
|
||||
|
||||
_flush()
|
||||
return result
|
||||
Reference in New Issue
Block a user