交换机防火墙等网络设备配置文件新旧对比用于发现配置变动

python示例

要看颜色的,下载 DEF_COLOR.py 放相同目录即可 http://t.csdnimg.cn/RFd2p

不要看的,修改打印部分代码即可

# -*- coding: utf8 -*-
import os, zipfile
from DEF_COLOR import *   ## 终端显示颜色


## 读取网络设备上下载的 startup.cfg 配置文件(文本文件)
## 读取 dis cu 查询配置保存下来的文本文件
def 读取文本文件内容(FILE_PATH):
    L_编码 = ['UTF8', 'GB2312', 'GBK', 'BIG5']
    TEXT = None
    if os.path.isfile(FILE_PATH):
        for 编码 in L_编码:
            try:
                #print(f"尝试 {编码}")
                f = open(FILE_PATH, 'r', encoding=编码)
                TEXT = f.read()
            except Exception as e:
                f.close()
                #打印_红(f"{编码}编码读取{FILE_PATH}失败{e}")
            else:
                f.close()
                #print(f"成功 TEXT={TEXT}")
                return(TEXT)
    else:
        打印_红(f"ERROR 文件 {FILE_PATH} 不存在")
        return(TEXT)
    打印_红(f"ERROR 文件 {FILE_PATH} 字符编码解码失败,超出{L_编码}范围")
    return(TEXT)

## 网络设备上下载的配置文件有压缩包类型,如:vrpcfg.zip 里面就一个配置文本文件 vrpcfg.cfg
def 读取ZIP中第一个文件(FILE_PATH):
    TEXT = None
    if os.path.isfile(FILE_PATH):
        if zipfile.is_zipfile(FILE_PATH):
            try:
                f = zipfile.ZipFile(FILE_PATH)
            except Exception as e:
                ERROR = f'ERROR zipfile 打开 {FILE_PATH} 失败 {e}'
                打印_红(ERROR)
                return()
            else:
                for i in f.namelist():
                    #print("ZIP内文件", i)
                    文件名, 扩展名 = os.path.splitext(i)
                    #print(文件名, 扩展名)
                    if i.endswith('/'):
                        #print(f"目录 {i}")
                        pass
                    else:
                        #print(f"文件 {i}")
                        try:
                            DATA = f.open(i)
                        except Exception as e:
                            ERROR = f'ERROR 打开 {i} 失败 {e}'
                            print(ERROR)
                        else:
                            TEXT = DATA.read().decode('GB2312')     # 一般是 GB2312
                            #print("TEXT", TEXT)
                            break
        else:
            打印_红(f"ERROR {FILE_PATH} 不是ZIP格式文件")
    else:
        打印_红(f"ERROR {FILE_PATH} 不存在或不是文件")
    return(TEXT)

def 前置空格数(TEXT):
    N = 0
    for i in TEXT:
        if i == ' ':
            N += 1
        else:
            break
    return(N)

def 配置文本分段分行(TEXT_CONF, SHOW=0):
    LL_CONF = []
    LL_配置行 = [[j for j in i.split('\r')] for i in TEXT_CONF.split('\n')] # 以换行符分段
    #打印_蓝(f"换行符分段 LL_配置行={LL_配置行}", SHOW)
    L_配置段 = []
    for L_配置行 in LL_配置行:
        #打印_红(f"{L_配置行}", SHOW)
        for 配置行 in L_配置行:
            if 配置行.strip() != '':   # 忽略空配置行
                if 配置行 == '#':      # 遇到分段符号'#'进行分段保存
                    if L_配置段 != []:
                        LL_CONF.append(L_配置段)
                        L_配置段 = []  # 重置为空列表
                else:
                    L_配置段.append(配置行)
    #打印_绿(f"{LL_CONF}", SHOW)
    #for i in LL_CONF:
    #    打印_蓝(f"{i}\n", SHOW)
    return(LL_CONF)

def 配置行转配置信息字典(LL_CONF, SHOW=0):
    打印_红("[配置行转配置信息字典]", SHOW)
    D_NET_CONF = {'GLOBAL':[]}
    for L_配置段 in LL_CONF:
        打印_红(f"L_配置段={L_配置段}", SHOW)
        KEY = 'GLOBAL'
        for TEXT_配置行 in L_配置段:
            配置行前置空格数 = 前置空格数(TEXT_配置行)
            打印_蓝(f"  前置空格数={配置行前置空格数} TEXT_配置行={TEXT_配置行}", SHOW)
            if 配置行前置空格数 == 0:
                KEY = TEXT_配置行           # 更新KEY
                if KEY not in D_NET_CONF:
                    D_NET_CONF[KEY] = []
                打印_绿(f"    KEY={KEY}", SHOW)
            else:
                D_NET_CONF[KEY].append(TEXT_配置行)
                打印_绿(f"    D_NET_CONF[{KEY}].append({TEXT_配置行})", SHOW)
    return(D_NET_CONF)

def 转换网络TXT配置文件(FILE_CONF_PATH, SHOW=0):
    D_NET_CONF = {}
    TEXT_CONF = 读取文本文件内容(FILE_CONF_PATH)
    打印_黄(f"TEXT_CONF={TEXT_CONF}", SHOW)
    if TEXT_CONF != None:
        LL_CONF = 配置文本分段分行(TEXT_CONF, SHOW)
        D_NET_CONF = 配置行转配置信息字典(LL_CONF, SHOW)
    return(D_NET_CONF)

def 转换网络ZIP配置文件(FILE_CONF_PATH, SHOW=0):
    D_NET_CONF = {}
    TEXT_CONF = 读取ZIP中第一个文件(FILE_CONF_PATH)
    打印_黄(f"TEXT_CONF={TEXT_CONF}", SHOW)
    if TEXT_CONF != None:
        LL_CONF = 配置文本分段分行(TEXT_CONF, SHOW)
        D_NET_CONF = 配置行转配置信息字典(LL_CONF, SHOW)
    return(D_NET_CONF)

def AAA用户信息转字典(LL_CONF, SHOW=0):
    打印_红("[AAA用户信息转字典]", SHOW)
    D_AAA = {'aaa':[]}
    KEY = 'aaa'
    for TEXT_配置行 in LL_CONF:
        配置行前置空格数 = 前置空格数(TEXT_配置行)
        打印_蓝(f"  前置空格数={配置行前置空格数} TEXT_配置行={TEXT_配置行}", SHOW)
        if 配置行前置空格数 == 1:
            KEY = ('aaa', TEXT_配置行)           # 更新KEY
            if KEY not in D_AAA:
                D_AAA[KEY] = []
            打印_绿(f"    KEY={KEY}", SHOW)
        else:
            D_AAA[KEY].append(TEXT_配置行)
            打印_绿(f"    D_AAA[{KEY}].append({TEXT_配置行})", SHOW)
    return(D_AAA)

def 配置字典再加工(D_NET_CONF, SHOW=0):
    # 删除一些无用信息(注释、查询命令等)
    for K in [i for i in D_NET_CONF]:
        if K == 'dis cu' or K[0] in ('*', '<', '[', '\x00'):
            del D_NET_CONF[K]
            打印_黄(f"  删除 {K}", SHOW)
        elif K[-7:] == ' +08:00':           # '2022-11-21 09:40:43.824 +08:00'
            del D_NET_CONF[K]
            打印_黄(f"  删除 {K}", SHOW)
        elif K[:27] == 'Info: The max number of VTY':   # Info: The max number of VTY users is 10, and the number
            del D_NET_CONF[K]
            打印_黄(f"  删除 {K}", SHOW)
        #else:
        #    打印_绿(f"  保留 {K}", SHOW)
    
    # 删除空全局配置
    if 'GLOBAL' in D_NET_CONF and D_NET_CONF['GLOBAL'] == []:
        del D_NET_CONF['GLOBAL']
        打印_黄(f"  删除 空 D_NET_CONF['GLOBAL']", SHOW)
    
    # 进一步细分,整理防火墙安全策略信息,方便对比策略变化
    if 'security-policy' in D_NET_CONF:
        #print(D_NET_CONF['security-policy'])
        LL_CONF = D_NET_CONF['security-policy']
        D_security_policy = 防火墙安全规则转字典(LL_CONF, SHOW)
        del D_NET_CONF['security-policy']
        for K in D_security_policy:
            D_NET_CONF[K] = D_security_policy[K]
    
    # 进一步细分,整理aaa用户信息,方便对比用户信息变化
    if 'aaa' in D_NET_CONF:
        LL_CONF = D_NET_CONF['aaa']
        D_AAA = AAA用户信息转字典(LL_CONF, SHOW)
        del D_NET_CONF['aaa']
        for K in D_AAA:
            D_NET_CONF[K] = D_AAA[K]

def 网络配置文件转配置字典(FILE_CONF_PATH, SHOW=0):
    FILE_NAME = os.path.basename(FILE_CONF_PATH)
    SP = FILE_NAME.split('.')
    if SP != [] and SP[-1].lower() == 'zip':
        D_NET_CONF = 转换网络ZIP配置文件(FILE_CONF_PATH, SHOW)
    else:
        D_NET_CONF = 转换网络TXT配置文件(FILE_CONF_PATH, SHOW)
    配置字典再加工(D_NET_CONF, SHOW)
    return(D_NET_CONF)

def 对比配置字典(D_OLD, D_NEW, SHOW=1):
    TEXT_对比结果 = ''
    if D_OLD != {} and D_NEW != {}:
        ## 对比新旧配置项是否有新增或删除
        P_KEY_D_OLD = set([K for K in D_OLD])   # 旧配置项
        P_KEY_D_NEW = set([K for K in D_NEW])   # 新配置项
        #print(f"P_KEY_D_OLD={P_KEY_D_OLD}")
        #print(f"P_KEY_D_NEW={P_KEY_D_NEW}")
        P_KEY_OLD_独有 = P_KEY_D_OLD - P_KEY_D_NEW
        P_KEY_NEW_独有 = P_KEY_D_NEW - P_KEY_D_OLD
        if P_KEY_OLD_独有 == P_KEY_NEW_独有 == set():
            P_KEY_共有 = P_KEY_D_OLD
            INFO = f"    [新旧配置项名] 一致"
            TEXT_对比结果 += INFO+'\n'
            打印_绿(INFO, SHOW)
        else:
            P_KEY_共有 = P_KEY_D_OLD & P_KEY_D_NEW
            INFO = f"    [新旧配置项名] 不同\n      P_KEY_OLD_独有 {P_KEY_OLD_独有}\n      P_KEY_NEW_独有 {P_KEY_NEW_独有}"
            打印_黄(INFO, SHOW)
            TEXT_对比结果 += INFO+'\n'
            for KEY in P_KEY_OLD_独有:
                INFO = f"      [删除配置项] {KEY} 【配置项内容】 {D_OLD[KEY]}"
                TEXT_对比结果 += INFO+'\n'
                打印_蓝(INFO, SHOW)
            for KEY in P_KEY_NEW_独有:
                INFO = f"      [新增配置项] {KEY} 【配置项内容】 {D_NEW[KEY]}"
                TEXT_对比结果 += INFO+'\n'
                打印_青(INFO, SHOW)
        ## 相同配置项对比配置内容是否有变化
        for KEY in P_KEY_共有:
            if D_OLD[KEY] != D_NEW[KEY]:
                P_OLD_配置行 = set(D_OLD[KEY])
                P_NEW_配置行 = set(D_NEW[KEY])
                if P_OLD_配置行 == P_NEW_配置行:
                    INFO = f"      [配置项] {KEY} 配置行内容有重复"
                    TEXT_对比结果 += INFO+'\n'
                    打印_红(INFO, SHOW)
                else:
                    INFO = f"      [变化配置项] {KEY}"
                    TEXT_对比结果 += INFO+'\n'
                    打印_红(INFO, SHOW)
                    for i in P_OLD_配置行-P_NEW_配置行:
                        INFO = f"        [DEL] {i}"
                        TEXT_对比结果 += INFO+'\n'
                        打印_蓝(INFO, SHOW)
                    for i in P_NEW_配置行-P_OLD_配置行:
                        INFO = f"        [ADD] {i}"
                        TEXT_对比结果 += INFO+'\n'
                        打印_青(INFO, SHOW)
    else:
        if D_OLD == {}:
            INFO = "    D_OLD 无有效内容"
            TEXT_对比结果 += INFO+'\n'
            打印_红(INFO)
        if D_NEW == {}:
            INFO = "    D_NEW 无有效内容"
            TEXT_对比结果 += INFO+'\n'
            打印_红(INFO)
    return(TEXT_对比结果)


## 测试
if __name__ == '__main__':
    
    def 测试_对比两个配置文件差异(FILE_CONF_OLD_PATH, FILE_CONF_NEW_PATH):
        D_NET_CONF_OLD = 网络配置文件转配置字典(FILE_CONF_OLD_PATH, SHOW=0)
        D_NET_CONF_NEW = 网络配置文件转配置字典(FILE_CONF_NEW_PATH, SHOW=0)
        打印_蓝(f"OLD {FILE_CONF_OLD_PATH}")
        打印_青(f"NEW {FILE_CONF_NEW_PATH}")
        TEXT_对比结果 = 对比配置字典(D_NET_CONF_OLD, D_NET_CONF_NEW, SHOW=1)
        #打印_紫(TEXT_对比结果)
    
    #FILE_CONF_OLD_PATH = '192.168.1.254.22.20240723.vrpcfg.zip'    # 压缩文件格式
    FILE_CONF_OLD_PATH = '192.168.1.254.22.20240724.startup.cfg'    # 文本文件格式
    FILE_CONF_NEW_PATH = '192.168.1.254.22.20240725.startup.cfg'
    测试_对比两个配置文件差异(FILE_CONF_OLD_PATH, FILE_CONF_NEW_PATH)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值