老男孩Day10作业:主机管理程序
一、作业需求:
1, 运行程序列出主机组或者主机列表
2,选择指定主机或主机组
3,选择主机或主机组传送文件(上传/下载)
4,充分使用多线程或多进程
5,不同主机的用户名,密码,端口可以不同
6,可向主机或主机组批量发布命令
7,可一次性执行多条操作命令
二、
一、作业需求: 1, 运行程序列出主机组或者主机列表(已完成) 2,选择指定主机或主机组(已完成) 3,选择主机或主机组传送文件(上传/下载)(已完成) 4,充分使用多线程或多进程(已完成) 5,不同主机的用户名,密码,端口可以不同(已完成) 6,可向主机或主机组批量发布命令(已完成) 7,可一次性执行多条操作命令(已完成) 二、博客地址:http://www.cnblogs.com/catepython/p/8872274.html 三、运行环境 操作系统:Win10 Python:3.6.4rcl Pycharm:2017.3.4 四、功能实现 1)实现所有基本需求 2)充分利用了面向对象式编程 五、测试 1)文件名为空判断 2)用户信息判断 3)指令格式化判断 4)上传/下载到指定路径判断 5)文件名/用户目录有效判断 六、备注
三、流程图
四、目录架构
五、核心代码
bin目录--程序开始
#-*-coding:utf-8 -*- # Author: D.Gray from core import main start = main.MyFabric() start.run()
conf目录
#-*-coding:utf-8 -*- # Author: D.Gray import os BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) #print(BASE_DIR) HOST_NAME_PATH = os.path.join(BASE_DIR,'db') #print(HOST_NAME_PATH) HOME_PATH = os.path.join(BASE_DIR,'home')
core目录
#-*-coding:utf-8 -*- # Author: D.Gray import os,sys,paramiko,threading,time,json from conf import setting import re class MyFabric(object): def __init__(self): self.run = self.run() def run(self): ''' 主界面运行函数 :return: ''' text = """ 欢迎来到Fabric主机管理界面 1.创建主机 2.删除主机 3.自动激活所有主机 4.开始远程操控 5.退出程序 """ while True: print(text) choose = input("请输入您的选择>>>:").strip() #print(type(choose)) self.dic = { '1':self.new_host, #创建主机模块 '2':self.delect_host, #删除主机模块 '3':self.auto_host, #激活主机模块 '4':self.action_host, #控制主机模块 '5':self.exit } if choose in self.dic: self.dic[choose]() else: print("请输入有效操作") def new_host(self): ''' 创建主机模块函数 :return: ''' #print('in the new_host') db_path = setting.HOST_NAME_PATH while True: name = input('请输入登录名称(输入n=返回上级)>>>:').strip() name_path = os.path.join(db_path,'%s.json'%(name)) #print(name_path) if os.path.exists(name_path): print('登录名称已存在') continue if name == 'n': return hostname = input('请输入主机名(输入n=返回上级)>>>:').strip() if hostname == 'n': return port = input('请输入端口号(输入n=返回上级)>>>:').strip() if port.isdigit(): port = int(port) else: print('端口号必须是整数') return if port == 'n': return username = input('请输入用户名(输入n=返回上级)>>>:').strip() if username == 'n': return password = input('请输入密码(输入n=返回上级)>>>:').strip() if password == 'n': return newhost_dic = { "name":name, #用户文件名(主机链接名称) "hostname":hostname, "username":username, "port":port, "password":password, "status": 0, #0---未激活,1---已激活,2--激活失败 } mesag = '''\033[33;1m 请确认录入信息:\n%s: 友情提示:请务必确保信息填写无误否则将无法正常进行管理登录操作!!! \033[0m'''%newhost_dic print(mesag) choose = input("开人确认录入信息(y/n)?:") if choose == 'n': print('信息确认失败将重新录入:') return elif choose == 'y': if os.path.isdir(name_path) == False: #判断用户文件是否已存在 with open(name_path,'w') as fw: json.dump(newhost_dic,fw,indent=4) #使用json.dump()函数将用户信息格式化写入 print('\033[32;1m信息载入完成\033[0m') break else: print('已存在改文件') else: print('输入有误请重新输入') def delect_host(self): ''' 删除主机模块函数 :return: ''' #print('in the delect_host') host_dic = self.add_host() #接收add_host()函数 {文件名:{用户字典信息}} 格式的返回值 host_list = [] print('\033[32;1m当前已存在主机名列表如下:\033[0m') for index,values in enumerate(host_dic.values()): #遍历用户字典信息 host_list.append(values['hostname']) print('%s-主机名:%s' % (index + 1, values['hostname'])) if len(host_list) != 0: choose = input("请输入你想删除的主机索引(输入n=返回上级):") if choose =='n': print('取消删除主机名') return if choose.isdigit(): choose = int(choose) if len(host_list) != 0: if choose <= len(host_list): # host_name(文件名称) # list(host_dic.keys())[choose-1](用户输入索引对应的主机名,也就是key) host_name = list(host_dic.keys())[choose-1] db_path = os.path.join(setting.HOST_NAME_PATH, host_name) os.remove(db_path) print('删除成功') else: print('\033[31;1m输入超出索引范围\033[0m') else: print('\033[31;1m当前主机索引不能在删除\033[0m') else: print('\033[31;1m请输入有效索引\033[0m') else: print('\033[31;1m当前已存在主机名列表为空请先创建主机\033[0m') def add_host(self): ''' 获取db文件夹中json文件中的主机名 :return:将已获取到的 {文件名:{用户字典信息}} 格式做为返回值 ''' names_list = [] #定义一个文件名列表 dic_list = [] #定义一个文件字典列表 db_dic = {} #定义一个db文件内容字典 db_path = setting.HOST_NAME_PATH ol = os.listdir(db_path) for i in ol: if i.endswith('.json'): #只获取后缀名为.json文件的信息 json_path = os.path.join(db_path,i) with open(json_path,'r') as f: fd = json.load(f) dic_list.append(fd) names_list.append(i) db_dic = dict(zip(names_list,dic_list)) return db_dic def auto_host(self): ''' 激活所有主机模块(尝试主机连接) :return: ''' #print('in the auto_host') text = """\033[32;1m 警告!程序准备开启多线程模式激活主机,请确保: 1,远程服务器处于开启状态 2,DNS或本地hosts映射能够解析远程服务器主机名 \033[0m""" while True: print(text) host_dic = self.add_host() host_list = [] dic_list = [] for index,values in enumerate(host_dic.values()): host_list.append(values['hostname']) dic_list.append(values) print('当前已存在主机名列表如下:') print('%s-主机名:%s' % (index + 1,values['hostname'])) if len(host_list) != 0: choose = input("\033[33;1m是否开始确认激活主机(y/n)?:\033[0m") if choose == 'n': print('\033[31;1m已取消激活\033[0m') return elif choose == 'y': for item in dic_list: #print(item,dic_list) print('\033[38;0m程序开始激活主机请稍后...\033[0m') time.sleep(1) dic = item t = threading.Thread(target=self.auto_action,args=(dic,)) #调用激活执行模块 t.setDaemon(True) t.start() while threading.active_count() != 1: pass print('当前活跃线程个数:',threading.activeCount()) break else: print('\033[31;1m输入有误请重新输入\033[0m') continue else: print('\033[31;1m当前已激活主机为空请先创建主机\033[0m') return def auto_action(self,dic): ''' 激活执行模块函数 :param dic: :return: ''' ssh = paramiko.SSHClient() ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) try: ssh.connect(hostname=dic['hostname'],port=dic['port'],username=dic['username'],password=dic['password']) except Exception as e: print('\033[31;1m主机: %s 连接尝试失败:%s\033[0m'%(dic['username'],e)) dic['status'] = 2 self.update_dic(dic) else: stdin, stdou, stdeer = ssh.exec_command('who') result = stdou.read() print('\033[32;1m主机: %s 连接尝试成功:\033[0m\n%s'%(dic['username'],result.decode())) dic['status'] = 1 self.update_dic(dic) #通过json.dump方法修改json文件中某一参数 finally: ssh.close() def update_dic(self,dic): ''' 更改主机状态函数 :param dic: :return: ''' db_path = os.path.join(setting.HOST_NAME_PATH,'%s.json'%(dic['name'])) with open(db_path,'w') as f: json.dump(dic,f,indent=4) #通过json.dump方法修改json文件中某一参数 return True def action_host(self): ''' 从已激活主机中选择相应的主机来进行操控 1、遍历出已激活主机列表 2、从激活主机列表中选择相应的主机进行操控(可多选) 3、将选择操控的主机添加至choose_lsit列表中并传参给action()函数 :return: ''' print('in the action_host') auto_dic = self.add_host() auto_list = [] #已激活主机列表 choose_list = [] # 用户选择操控主机列表 for info in auto_dic.values(): if info['status'] == 1: auto_list.append(info) if len(auto_list) != 0: while True: for index,values in enumerate(auto_list): print('%s-主机名:%s 已激活'%(index+1,values['hostname'])) if len(auto_list) != 0: choose = input("\033[38;1m请输入你想控制的主机索引(可多选,输入n=返回上级):\033[0m").strip() if choose == 'n': print('\033[31;1m取消控制主机\033[0m') return if choose.isdigit(): #限制索引只能是数字 chooses = list(set(choose)) #去重多选 for index in chooses: index = int(index) if index <= len(auto_list) and index !=0: #限制输入的索引在有效范围内 choose_list.append(auto_list[index-1]) else: print("\033[31;1m有一个索引超出范围\033[0m") choose_list.clear() return else: print('\033[31;1m请输入有效索引\033[0m') return else: print('\033[31;1m当前已存在主机名列表为空请先激活主机\033[0m') self.action(choose_list) return else: print('\033[31;1m当前已激活主机为空请先创建主机\033[0m') return def action(self,choose_list): ''' 1、接收action_host()函数传来的 已操控主机列表([{用户1字典信息},{用户2字典信息}])格式 2、遍历用户字典信息 3、判断用户选择操作情况, 如put---进入put上传文件函数、get---进入get下载文件函数、其他进入cmd命令行操作函数 :param choose_list: :return: ''' #print('in the action:',choose_list) mesg = '''\033[33;1m help帮助提示: 1.程序的Home目录是本地文件目录 2,输入put向远程主机上传文件 3,输入get向远程主机下载文件 4,输入其他直接向远程主机发布命令 ''' while True: print(mesg) if len(choose_list) != 0: print('\033[32;1m您正在操控%s台主机,如下:\033[0m'%(len(choose_list))) for index,info in enumerate(choose_list): print('%s-主机名:%s'% (index+1, info['hostname'])) command = input("\033[38;1m请输入你想执行的命令(输入n=返回上级,输入任意键进入cmd模式)>>:\033[0m") dic = info if command == 'n': return if hasattr(self,command): getattrs = getattr(self,command) getattrs(dic) else: print('准备向远程主机发布命令...请稍后') t = threading.Thread(target=self.execute_command,args=(dic,)) t.setDaemon(True) t.start() while threading.active_count() != 1: pass print('当前活跃线程个数:', threading.activeCount()) break def put(self,dic): ''' 上传文件函数 1、判断home目录中是否存在用户文件夹 2、输入需上传的文件名 3、输入所需上传到服务器的地址路径 4、调用put_action()函数开始上传文件 :param dic: :return: ''' print('准备上传文件!!!') home_path = os.path.join(setting.HOME_PATH,dic['name']) while True: if os.path.exists(home_path): filename = input('\033[37;1m请输入需上传文件名(例:xxx.txt)输入n返回上级菜单>>>:\033[0m') if filename == 'n': print('\033[31;1m取消上传文件\033[0m') return file_path = os.path.join(home_path,filename) if os.path.isfile(file_path): remote = input("\033[37;1m你想将文件保存到远程服务器路径(例如:/etc/)>>>:\033[0m") remote_path = os.path.join('%s/'%remote,filename) t = threading.Thread(target=self.put_action,args=(dic,file_path,remote_path,)) t.setDaemon(True) print('程序准备开始上传文件!!!') time.sleep(1) t.start() while threading.activeCount() != 1: pass return # print('当前活跃线程个数:', threading.activeCount()) else: print('\033[31;1m文件没有找到,请重新输入!\033[0m') continue else: print('\033[31;1m未找到指定用户目录\033[0m') time.sleep(1) print('\033[31;1m正在创建请稍后...\033[0m') time.sleep(1) os.mkdir(home_path) print('\033[32;1m用户目录创建成功!!!\033[0m') return def get(self,dic): ''' 下载文件 1、判断home目录中是否存在用户文件夹 2、输入服务端地址路径 3、输入下载的文件名 4、调用get_action()函数开始下载文件 :param dic:接收action函数传来的用户字典信息 :return: ''' print('准备下载文件!!!') home_path = os.path.join(setting.HOME_PATH, dic['name']) #用户目录路径 if os.path.exists(home_path): remote = input("\033[37;1m请输入想要下载的远程服务器文件绝对路径(例如:/etc/hosts/):\033[0m") remote_file = input("\033[37;1m请输入想要下载的远程服务器文件名(例如:xxx.txt):\033[0m") remote_path = os.path.join('%s/'%remote,remote_file) #拼接服务端路径 t = threading.Thread(target=self.get_action,args=(dic,home_path,remote_path,remote_file,)) t.setDaemon(True) t.start() while threading.activeCount() != 1: pass return else: print('\033[31;1m未找到指定用户目录\033[0m') time.sleep(1) print('\033[31;1m正在创建请稍后...\033[0m') time.sleep(1) os.mkdir(home_path) print('\033[32;1m用户目录创建成功!!!\033[0m') return def put_action(self,*args): ''' 上传文件执行函数 :param args: :return: ''' #print('in the put_action:',*args) dic = args[0] #主机用户字典信息 file_path = args[1] #本地用户目录路径 remote_path = args[2] #服务端路径 transport = paramiko.Transport((dic['hostname'], int(dic['port']))) try: transport.connect(username=dic['username'],password=dic['password']) sftp = paramiko.SFTPClient.from_transport(transport) sftp.put(file_path,remote_path) except Exception as e: print('\033[31;1m主机名:[%s]文件上传失败...失败原因:\n%s\033[0m'%(dic['hostname'],e)) else: print('\033[32;1m主机名:[%s]文件上传成功\033[0m' % (dic['hostname'])) finally: transport.close() def get_action(self,*args): ''' 下载文件执行函数 :param args: :return: ''' #print('in the get_action:',*args) dic = args[0] #主机信息字典 home_path = args[1] #本地目录路径 remote_path = args[2] #服务器路径 remote_file = args[3] #从服务器下载的文件名 file_path = os.path.join(home_path,remote_file) #下载倒本地目录路径 transport = paramiko.Transport((dic['hostname'],int(dic['port']))) try: transport.connect(username=dic['username'],password=dic['password']) sftp = paramiko.SFTPClient.from_transport(transport) sftp.get(remote_path,file_path) except Exception as e: print('\033[31;1m主机名:[%s]文件下载失败...失败原因:\n%s\033[0m' % (dic['hostname'], e)) else: print('\033[32;1m主机名:[%s]文件下载成功\033[0m' % (dic['hostname'])) finally: transport.close() def execute_command(self,dic): ''' cmd模式 :param dic: :return: ''' #print('in the execute_command:%s '% (dic)) ssh = paramiko.SSHClient() ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy) try: ssh.connect(hostname=dic['hostname'],port=dic['port'],username=dic['username'],password=dic['password']) except Exception as e: print('\033[31;1m主机: %s 连接尝试失败:%s\033[0m' % (dic['hostname'], e)) else: while True: command = input("\033[38;1m请输入你想执行的命令(输入n=返回上级)>>:\033[0m") if command == 'n': return else: stdin, stdou, stdeer = ssh.exec_command(command) erro = stdeer.read() output =stdou.read() if len(erro) !=0: print("\033[31;1m主机:%s 执行%s命令时出错:%s\033[0m"%(dic['hostname'],command,(erro.decode()))) #return False else: if len(output.decode()) == 0: print('该命令无返回结果') else: print("\033[32;1m主机:%s 执行[%s]命令结果如下:\033[0m\n%s" % (dic['hostname'], command, (output.decode()))) finally: ssh.close() def exit(self): #print('in the exit') exit('程序退出')
db目录
{ "name": "admin_kyo", "hostname": "192.168.111.128", "username": "admin_kyo", "port": 22, "password": "admin1988", "status": 1 }