mirror of
https://github.com/GNS3/gns3-server.git
synced 2026-08-27 20:40:13 +03: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
|
||||
|
||||
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
|
||||
- ⚠️ Warn user before making major changes
|
||||
- ⚠️ 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
|
||||
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)
|
||||
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
|
||||
try:
|
||||
dynamic_nr = self._initialize_nornir(hosts_data)
|
||||
@ -445,6 +472,60 @@ class ExecuteMultipleDeviceConfigCommands(BaseTool):
|
||||
|
||||
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(
|
||||
self, device_config_list: list[dict[str, Any]]
|
||||
) -> dict[str, list[str]]:
|
||||
|
||||
@ -211,6 +211,26 @@ class ExecuteMultipleDeviceCommands(BaseTool):
|
||||
logger.error("Failed to prepare device hosts data: %s", 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
|
||||
try:
|
||||
dynamic_nr = self._initialize_nornir(hosts_data)
|
||||
|
||||
@ -104,20 +104,25 @@ def get_device_ports_from_topology(
|
||||
elif tag.startswith("platform:"):
|
||||
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:
|
||||
device_type = "cisco_ios_telnet"
|
||||
logger.debug(
|
||||
"Device '%s': device_type not found in tags, using default: "
|
||||
"cisco_ios_telnet",
|
||||
device_name,
|
||||
)
|
||||
else:
|
||||
logger.debug(
|
||||
"Device '%s': extracted device_type=%s from tags",
|
||||
device_name,
|
||||
device_type,
|
||||
error_msg = (
|
||||
f"Device '{device_name}': device_type tag not found. "
|
||||
f"Please add 'device_type:<type>' tag to this device in GNS3. "
|
||||
f"Current tags: {tags}"
|
||||
)
|
||||
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:
|
||||
platform = "cisco_ios"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user