这是什么?
这是一个基于 Python 和 Netmiko 开发的网络设备自动化运维工具。它支持 JSON 配置文件多模板继承,能够并发地对多台网络设备执行命令巡检或配置下发,并自动生成详细的任务日志。
项目结构
.
├── configLoader.py # 配置解析核心逻辑(支持多模板合并)
├── main.py # 主程序(并发执行与逻辑控制)
├── devices.json # 设备清单与模板配置文件
├── commands.txt # 待执行的命令列表
└── logs/ # 运行后自动生成的日志目录
快速开始
1. 安装依赖
pip install netmiko
2. 配置设备信息 (devices.json)
{
"templates": {
"h3c_telnet": {
"device_type": "hp_comware_telnet",
"username": "",
"password": "old_password",
"port": 23
},
"new_password": {
"password": "special_password_2024"
},
"h3c_ssh":{
"device_type": "hp_comware",
"username": "admin",
"password": "password",
"port": 23
}
},
"device_list": [
{ # 多模板格式
"host": "172.16.100.11",
"use_template": ["h3c_telnet", "new_password"]
},
{ # 单模板格式
"host": "172.16.100.12",
"use_template": "h3c_ssh"
},
{ # 完整格式
'device_type': 'hp_comware_telnet',
'password': 'Password@_',
'port': 23,
'host': '172.16.100.13'
}
]
}
3. 编写命令 (commands.txt)
display version
display ip interface brief
4. 运行程序
python main.py
统计报告示例
------------------------------
统计报告:
- 总计设备: 12
- 成功数量: 11
- 失败数量: 1
------------------------------
[DONE] 批量任务结束, 日志已保存至 logs
完整代码
main.py
import os
import threading # 引入线程锁
from netmiko import ConnectHandler
from concurrent.futures import ThreadPoolExecutor, as_completed
from configLoader import load_and_merge_config
# 配置常量
DEVICE_CONFIG_FILE = 'devices.json'
COMMAND_FILE = 'commands.txt'
LOG_DIR = 'logs'
# 初始化计数器和锁
success_count = 0
error_count = 0
counter_lock = threading.Lock()
if not os.path.exists(LOG_DIR):
os.makedirs(LOG_DIR)
def run_commands_on_device(device):
global success_count, error_count
host = device.get('host', 'Unknown')
log_path = os.path.join(LOG_DIR, f"{host}.log")
try:
if not os.path.exists(COMMAND_FILE):
raise FileNotFoundError(f"找不到命令文件: {COMMAND_FILE}")
with open(COMMAND_FILE, 'r', encoding='utf-8') as f:
commands = [l.strip() for l in f if l.strip()]
print(f"[+] 正在连接: {host}...")
with ConnectHandler(**device, global_delay_factor=2) as net_connect:
# 自动处理分屏显示
d_type = device.get('device_type', '')
if any(x in d_type for x in ["hp_comware", "huawei"]):
net_connect.send_command("screen-length 0 temporary")
elif any(x in d_type for x in ["cisco_ios", "ruijie"]):
net_connect.send_command("terminal length 0")
output = f"--- 执行回显: {host} ---\n"
for cmd in commands:
print(f" [{host}] 执行 -> {cmd}")
res = net_connect.send_command(cmd, read_timeout=20, expect_string=r'[>\]#]')
output += f"\n> {cmd}\n{res}\n"
with open(log_path, 'w', encoding='utf-8') as f_log:
f_log.write(output)
print(f"[OK] {host} 任务处理完毕。")
# 统计成功
with counter_lock:
success_count += 1
return True
except Exception as e:
err_msg = f"[ERROR] {host} 异常: {str(e)}"
print(err_msg)
with open(log_path, 'w', encoding='utf-8') as f_log:
f_log.write(err_msg)
# 统计失败
with counter_lock:
error_count += 1
return False
def main():
devices = load_and_merge_config(DEVICE_CONFIG_FILE)
if not devices:
print("[-] 未获取到有效设备信息,请检查配置。")
return
total_devices = len(devices)
print(f"[*] 准备对 {total_devices} 台设备进行并发操作...\n")
# 使用 ThreadPoolExecutor
with ThreadPoolExecutor(max_workers=20) as executor:
# 提交所有任务
executor.map(run_commands_on_device, devices)
# 任务结束后打印汇总
print("-" * 30)
print(f"统计报告:")
print(f" - 总计设备: {total_devices}")
print(f" - 成功数量: {success_count}")
print(f" - 失败数量: {error_count}")
print("-" * 30)
print(f"\n[DONE] 批量任务结束, 日志已保存至 {LOG_DIR}")
if __name__ == "__main__":
main()
configLoader.py
import json
from copy import deepcopy
def load_and_merge_config(file_path):
"""
解析 JSON 配置文件,支持多模板继承(字符串或列表)和无用户名处理
"""
try:
with open(file_path, 'r', encoding='utf-8') as f:
data = json.load(f)
templates = data.get("templates", {})
device_list = data.get("device_list", [])
final_devices = []
for dev in device_list:
# 1. 初始化基础配置
merged_template_config = {}
# 2. 获取模板引用(可能是 字符串 或 列表)
template_refs = dev.get("use_template", [])
# 统一转为列表处理,方便后续循环
if isinstance(template_refs, str):
template_refs = [template_refs]
# 3. 逐个合并模板:后面的模板会覆盖前面的
for t_name in template_refs:
template_data = templates.get(t_name, {})
# 使用 deepcopy 确保多层字典嵌套时不会互相污染
merged_template_config.update(deepcopy(template_data))
# 4. 合并设备自身的参数:设备自身优先级最高
full_config = {**merged_template_config, **dev}
# 5. 清理辅助字段
full_config.pop("use_template", None)
# 6. 针对无用户名 Telnet 的特殊兼容处理
# 只要 username 为空、全空格或不存在,就删除该键
if not str(full_config.get('username', '')).strip():
full_config.pop('username', None)
final_devices.append(full_config)
return final_devices
except FileNotFoundError:
print(f"[-] 错误: 找不到配置文件 {file_path}")
return []
except json.JSONDecodeError:
print(f"[-] 错误: {file_path} 格式不正确")
return []
except Exception as e:
print(f"[-] 异常: {e}")
return []
# 测试运行 生产环境请注释
# devices = load_and_merge_config('devices.json')
# for d in devices:
# print(d)