mirror of
https://github.com/GNS3/gns3-server.git
synced 2026-10-28 11:12:26 +02:00
feat(agent): enhance configuration safety and multiline command handling
- Add explicit AAA/password configuration prohibition to safety reminders - Implement multiline command expansion for banner and similar commands - Add error handling for devices with missing device_type tags - Improve logging for configuration errors and multiline expansions
This commit is contained in:
parent
364ff93783
commit
8d9fce1c12
@ -165,6 +165,7 @@ vlan 10
|
|||||||
# SAFETY REMINDERS
|
# SAFETY REMINDERS
|
||||||
|
|
||||||
While you have configuration permissions, exercise caution:
|
While you have configuration permissions, exercise caution:
|
||||||
|
- ⚠️ **FORBIDDEN**: AAA/password config (enable secret, username, aaa new-model, service password-encryption, line vty) - Provide guidance only
|
||||||
- ⚠️ Avoid destructive commands (reload, erase, format) unless explicitly requested
|
- ⚠️ Avoid destructive commands (reload, erase, format) unless explicitly requested
|
||||||
- ⚠️ Warn user before making major changes
|
- ⚠️ Warn user before making major changes
|
||||||
- ⚠️ Recommend backup for critical configurations
|
- ⚠️ Recommend backup for critical configurations
|
||||||
|
|||||||
@ -196,6 +196,13 @@ class ExecuteMultipleDeviceConfigCommands(BaseTool):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Expand multiline commands (e.g., banner commands with embedded newlines)
|
||||||
|
# This converts commands like "banner motd #\nline1\nline2\n#" into
|
||||||
|
# separate commands: ["banner motd #", "line1", "line2", "#"]
|
||||||
|
device_configs_list = self._expand_multiline_commands(
|
||||||
|
device_configs_list
|
||||||
|
)
|
||||||
|
|
||||||
# Create a mapping of device names to their configuration commands
|
# Create a mapping of device names to their configuration commands
|
||||||
device_configs_map = self._configs_map(device_configs_list)
|
device_configs_map = self._configs_map(device_configs_list)
|
||||||
|
|
||||||
@ -208,6 +215,26 @@ class ExecuteMultipleDeviceConfigCommands(BaseTool):
|
|||||||
logger.error("Failed to prepare device hosts data: %s", e)
|
logger.error("Failed to prepare device hosts data: %s", e)
|
||||||
return [{"error": str(e)}]
|
return [{"error": str(e)}]
|
||||||
|
|
||||||
|
# Check if any devices have errors (e.g., missing device_type tag)
|
||||||
|
error_devices = {
|
||||||
|
name: data
|
||||||
|
for name, data in hosts_data.items()
|
||||||
|
if "error" in data
|
||||||
|
}
|
||||||
|
if error_devices:
|
||||||
|
logger.error(
|
||||||
|
"Devices with configuration errors: %s",
|
||||||
|
list(error_devices.keys())
|
||||||
|
)
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
"device_name": name,
|
||||||
|
"status": "failed",
|
||||||
|
"error": data["error"]
|
||||||
|
}
|
||||||
|
for name, data in error_devices.items()
|
||||||
|
]
|
||||||
|
|
||||||
# Initialize Nornir
|
# Initialize Nornir
|
||||||
try:
|
try:
|
||||||
dynamic_nr = self._initialize_nornir(hosts_data)
|
dynamic_nr = self._initialize_nornir(hosts_data)
|
||||||
@ -445,6 +472,60 @@ class ExecuteMultipleDeviceConfigCommands(BaseTool):
|
|||||||
|
|
||||||
return filtered_list, blocked_commands_map
|
return filtered_list, blocked_commands_map
|
||||||
|
|
||||||
|
def _expand_multiline_commands(
|
||||||
|
self, device_configs_list: list[dict[str, Any]]
|
||||||
|
) -> list[dict[str, Any]]:
|
||||||
|
"""
|
||||||
|
Expand commands that contain embedded newline characters.
|
||||||
|
|
||||||
|
Some commands (like banner) are passed as single strings with embedded
|
||||||
|
newlines. This method splits them into separate commands for proper
|
||||||
|
execution by netmiko_send_config.
|
||||||
|
|
||||||
|
Note: netmiko_send_config automatically handles entering config mode,
|
||||||
|
so any config mode entry commands (configure terminal, system-view, etc.)
|
||||||
|
are left as-is and will be safely ignored by the device.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
Input: ["banner motd #\nline1\nline2\n#", "exit"]
|
||||||
|
Output: ["banner motd #", "line1", "line2", "#", "exit"]
|
||||||
|
|
||||||
|
Args:
|
||||||
|
device_configs_list: List of device configs with config_commands.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List of device configs with multiline commands expanded.
|
||||||
|
"""
|
||||||
|
expanded_list = []
|
||||||
|
|
||||||
|
for device_config in device_configs_list:
|
||||||
|
device_name = device_config["device_name"]
|
||||||
|
config_commands = device_config["config_commands"]
|
||||||
|
|
||||||
|
expanded_commands = []
|
||||||
|
for cmd in config_commands:
|
||||||
|
# Check if command contains embedded newlines
|
||||||
|
if "\n" in cmd:
|
||||||
|
# Split by newline and expand
|
||||||
|
lines = cmd.split("\n")
|
||||||
|
# Filter out empty lines but keep all content lines
|
||||||
|
expanded_commands.extend([line for line in lines if line.strip()])
|
||||||
|
logger.info(
|
||||||
|
"Device %s: Expanded multiline command (%d lines)",
|
||||||
|
device_name,
|
||||||
|
len(lines),
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
# Keep single-line commands as-is
|
||||||
|
expanded_commands.append(cmd)
|
||||||
|
|
||||||
|
# Create updated device config
|
||||||
|
expanded_config = device_config.copy()
|
||||||
|
expanded_config["config_commands"] = expanded_commands
|
||||||
|
expanded_list.append(expanded_config)
|
||||||
|
|
||||||
|
return expanded_list
|
||||||
|
|
||||||
def _configs_map(
|
def _configs_map(
|
||||||
self, device_config_list: list[dict[str, Any]]
|
self, device_config_list: list[dict[str, Any]]
|
||||||
) -> dict[str, list[str]]:
|
) -> dict[str, list[str]]:
|
||||||
|
|||||||
@ -211,6 +211,26 @@ class ExecuteMultipleDeviceCommands(BaseTool):
|
|||||||
logger.error("Failed to prepare device hosts data: %s", e)
|
logger.error("Failed to prepare device hosts data: %s", e)
|
||||||
return [{"error": str(e)}]
|
return [{"error": str(e)}]
|
||||||
|
|
||||||
|
# Check if any devices have errors (e.g., missing device_type tag)
|
||||||
|
error_devices = {
|
||||||
|
name: data
|
||||||
|
for name, data in hosts_data.items()
|
||||||
|
if "error" in data
|
||||||
|
}
|
||||||
|
if error_devices:
|
||||||
|
logger.error(
|
||||||
|
"Devices with configuration errors: %s",
|
||||||
|
list(error_devices.keys())
|
||||||
|
)
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
"device_name": name,
|
||||||
|
"status": "failed",
|
||||||
|
"error": data["error"]
|
||||||
|
}
|
||||||
|
for name, data in error_devices.items()
|
||||||
|
]
|
||||||
|
|
||||||
# Initialize Nornir
|
# Initialize Nornir
|
||||||
try:
|
try:
|
||||||
dynamic_nr = self._initialize_nornir(hosts_data)
|
dynamic_nr = self._initialize_nornir(hosts_data)
|
||||||
|
|||||||
@ -104,20 +104,25 @@ def get_device_ports_from_topology(
|
|||||||
elif tag.startswith("platform:"):
|
elif tag.startswith("platform:"):
|
||||||
platform = tag.split(":", 1)[1].strip()
|
platform = tag.split(":", 1)[1].strip()
|
||||||
|
|
||||||
# Use defaults if not found in tags
|
# Return error if device_type not found in tags
|
||||||
|
# Using a default would cause command execution errors
|
||||||
if device_type is None:
|
if device_type is None:
|
||||||
device_type = "cisco_ios_telnet"
|
error_msg = (
|
||||||
logger.debug(
|
f"Device '{device_name}': device_type tag not found. "
|
||||||
"Device '%s': device_type not found in tags, using default: "
|
f"Please add 'device_type:<type>' tag to this device in GNS3. "
|
||||||
"cisco_ios_telnet",
|
f"Current tags: {tags}"
|
||||||
device_name,
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
logger.debug(
|
|
||||||
"Device '%s': extracted device_type=%s from tags",
|
|
||||||
device_name,
|
|
||||||
device_type,
|
|
||||||
)
|
)
|
||||||
|
logger.error(error_msg)
|
||||||
|
hosts_data[device_name] = {
|
||||||
|
"error": error_msg
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
|
||||||
|
logger.debug(
|
||||||
|
"Device '%s': extracted device_type=%s from tags",
|
||||||
|
device_name,
|
||||||
|
device_type,
|
||||||
|
)
|
||||||
|
|
||||||
if platform is None:
|
if platform is None:
|
||||||
platform = "cisco_ios"
|
platform = "cisco_ios"
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user