본문 바로가기
Python

paramiko ssh 접속 개선판 (수정3)

by Ao1 2024. 10. 18.
import os
import sys
sys.path.append(os.getcwd() + "\lib")

import paramiko
from paramiko import SSHException, AuthenticationException

import datetime
import time
import pandas as pd
import threading
import PySimpleGUI as psg
from queue import Queue

       
# 엑셀 읽어오기
def xlsx_read(pd_name):  
    pd_xlsx = pd.read_excel(pd_name, engine = "openpyxl", sheet_name=0)
   
    try:
        pd_name = pd_xlsx['hostname'].values.tolist()
        pd_ip = pd_xlsx['ip'].values.tolist()
        dev_len = str(len(pd_ip))
       
        filenumber = 1
        log_file_path = "./Log(%d)[%s]"%(filenumber,dev_len)     # 폴더 생성 이름 정의

        if not os.path.isdir("./Log[%s]"%(dev_len)) and not os.path.isdir(log_file_path):     # if로 정의된 폴더가 없으면
            log_file_path = "./Log[%s]"%(dev_len)
            os.mkdir(log_file_path)

        else:       # 그 외
            while os.path.isdir(log_file_path):     # 중복 폴더가 없을 때 까지
                log_file_path = "./Log(%d)[%s]"%(filenumber,dev_len)
                filenumber += 1
            os.mkdir(log_file_path)     # 폴더 생성
       
       
        return pd_ip, pd_name, log_file_path
    except:
        psg.popup("", "          파일이 잘못되었습니다.          \x00", "",title='정보')
       

# Choice
def Run():
    if values['-Only_T-']:
        DEVICES, DEVICES_NAME, LOG_FILE_PATH = xlsx_read(values['-File-'])
        USER_TAC = values['-TacacsID-']
        PASSWD_TAC = values['-TacacsPW-']
        USER_LOCAL = ''
        PASSWD_LOCAL = ''
        PORT = values['-Port-']
        COMMAND = values['-CommandLine-']
           
    if values['-Only_L-']:
        DEVICES, DEVICES_NAME, LOG_FILE_PATH = xlsx_read(values['-File-'])
        USER_TAC = ''
        PASSWD_TAC = ''
        USER_LOCAL = values['-LocalID-']
        PASSWD_LOCAL = values['-LocalPW-']
        PORT = values['-Port-']
        COMMAND = values['-CommandLine-']
                     
    if values['-T_W_L-']:
        DEVICES, DEVICES_NAME, LOG_FILE_PATH = xlsx_read(values['-File-'])
        USER_TAC = values['-TacacsID-']
        PASSWD_TAC = values['-TacacsPW-']
        USER_LOCAL = values['-LocalID-']
        PASSWD_LOCAL = values['-LocalPW-']
        PORT = values['-Port-']
        COMMAND = values['-CommandLine-']
           
    return DEVICES, DEVICES_NAME, LOG_FILE_PATH, USER_TAC, PASSWD_TAC, USER_LOCAL, PASSWD_LOCAL, PORT, COMMAND

# 진행중 표시 창
def Processing_Window():
    complet = 0
    err_que = Queue()
   
    # SSH Connect
    def sessions(DEVICE, DEVICE_NAME, USER, PASSWD_SECRET, USER_LOCAL, PASSWD_LOCAL, PORT):
        lock = threading.Lock()
        nonlocal complet
       
        dtime = 0
        login_Cnt = 0
        Cmd_Cnt = 0
       
        SSH_Client = paramiko.SSHClient()
        SSH_Client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
       
        try:
            SSH_Client.connect(DEVICE, username=USER, password=PASSWD_SECRET, port=PORT, allow_agent=False, look_for_keys=False, timeout=300 ,auth_timeout=60, channel_timeout=60)
            SHELL = SSH_Client.invoke_shell(width = 999)
           
            CMD = COMMAND.split('\n')
           
            cv = ''

            # enable 모드 진입
            while True:
                dtime += 1
                if SHELL.send_ready():
                    if login_Cnt == 0:
                        time.sleep(1)
                        SHELL.send('en\n')
                        login_Cnt = 1
                       
                    if login_Cnt == 1:
                        time.sleep(1)
                        SHELL.send('%s\n\n\n'%PASSWD_SECRET)
                        login_Cnt = 2
                if login_Cnt == 2:
                    break
                if dtime == 300:
                    break
           
            # 버퍼 초기화
            while not SHELL.recv_ready():
                time.sleep(1)
            tmp = SHELL.recv(65535).decode(encoding='utf-8')
           
            # user 모드 확인 버퍼
            SHELL.send('\n')
            while not SHELL.recv_ready():
                time.sleep(1)
            tmp = SHELL.recv(65535).decode(encoding='utf-8')
           
            # 중간 종료
            if dtime == 300 or '>' in tmp:
                err_que.put(DEVICE)
                SHELL.close()
                SSH_Client.close()
           
            else:
                SHELL.send('\n')            
                while True:
                    if SHELL.send_ready():
                        SHELL.send('%s\n'%CMD[Cmd_Cnt])
                        Cmd_Cnt += 1
                       
                    if Cmd_Cnt == len(CMD):
                        break
               
                while True:
                    time.sleep(5)
                    if SHELL.recv_ready():
                        cv += SHELL.recv(65535).decode(encoding='utf-8')
                    else:
                        break
                   
                SHELL.close()
                SSH_Client.close()
               
                cv = cv.replace('\r\n','\n')
               
                dev_log = open('%s\%s.log'%(LOG_FILE_PATH, DEVICE_NAME), "w+", encoding='utf-8')
                dev_log.write(cv)
                dev_log.close()
               
                lock.acquire()
                complet = complet + 1
                lock.release()

           
        except AuthenticationException:
            if Account_Mode:
                sessions(DEVICE, DEVICE_NAME, USER_LOCAL, PASSWD_LOCAL, '', '', PORT)
            else:
                err_que.put(DEVICE)
                lock.acquire()
                complet = complet + 1
                lock.release()

        except (SSHException, TimeoutError):
            err_que.put(DEVICE)
            lock.acquire()
            complet = complet + 1
            lock.release()

        except:
            err_que.put(DEVICE)
            lock.acquire()
            complet = complet + 1
            lock.release()

###################################################################################################################################################    
    # SSH Processing
    def SSH_Threading():
        dc = 0
        thrs = []
                       
        while dc < len(DEVICES):
            thread_2 = threading.Thread(target=sessions, args=(DEVICES[dc], DEVICES_NAME[dc], USER_TAC, PASSWD_TAC, USER_LOCAL, PASSWD_LOCAL, PORT))
            thread_2.start()
            dc += 1

            time.sleep(.5)
           
            thrs.append(thread_2)
           
        for thr in thrs:
            thr.join()

###################################################################################################################################################    
    layout_1 = [
        [psg.Frame('',[
        [psg.Text('진행중     ',key='-now-',font=('bold',20))]],element_justification='c', vertical_alignment='c', border_width=0)]]
   
    window_1 = psg.Window('', layout_1, grab_anywhere=True, finalize=True, no_titlebar=True, keep_on_top=True)
   
    thread_1 = threading.Thread(target=SSH_Threading)
    thread_1.start()
   
    tick = 1

    while True:
        event, values = window_1.read(timeout=1000) # 1초
       
        # 중지
        if event == '-Stop-':
            sys.exit()

        # 스레드 1번이 살아 있으면 조건문 수행
        if thread_1.is_alive():
            percent = ((complet/len(DEVICES)) * 100)
           
            if (tick%3) == 1:
                window_1['-now-'].update('진행중.     \n{}% 완료'.format(int(percent)))
               
            if (tick%3) == 2:
                window_1['-now-'].update('진행중..    \n{}% 완료'.format(int(percent)))
               
            if (tick%3) == 0:
                window_1['-now-'].update('진행중...   \n{}% 완료'.format(int(percent)))
               
            tick += 1
            continue
        else:
            break
       
    window_1.close()
   
    return err_que
   
if __name__ == "__main__":
    CHECK_TF = True
   
    psg.theme('DarkBlack1')

    # GUI 구성 레이아웃
    layout = [[psg.Frame(layout=[
        [psg.Text('ID',justification='center',size=(10,1),pad=(0,13)),psg.Input(key='-TacacsID-',size=(30,1),pad=(0,13))],
        [psg.Text('PW',justification='center',size=(10,1),pad=(0,13)),psg.Input(password_char='*',key='-TacacsPW-',size=(30,1),pad=(0,13))],
        [psg.Text('Local ID',justification='center',size=(10,1),pad=(0,13)),psg.Input(key='-LocalID-',size=(30,1),pad=(0,13))],
        [psg.Text('Local PW',justification='center',size=(10,1),pad=(0,13)),psg.Input(password_char='*',key='-LocalPW-',size=(30,1),pad=(0,13))],
        [psg.Text('Port',justification='center',size=(10,1),pad=(0,10)),psg.Input(key='-Port-',default_text='22',size=(30,1),pad=(0,13))],],
                         title='계정 정보 입력',element_justification='c',vertical_alignment='t',size=(350,250)),
               psg.Frame('',[
                   [psg.Frame(layout=[
                       [psg.Radio('ID/PW Only','account_info',key='-Only_T-',enable_events=True)],
                       [psg.Radio('Local Only','account_info',key='-Only_L-',enable_events=True)],
                       [psg.Radio('ID/PW and Local','account_info',key='-T_W_L-',circle_color='yellow', text_color='yellow',enable_events=True,default=True)]],
                              title='계정 모드 설정',vertical_alignment='t')]],vertical_alignment='c',element_justification='c',border_width=0),
              psg.Frame('',[
                       [psg.Frame(layout=[[psg.Text(size=(18,1), justification='center',font=('Helvetica', 15, 'bold'),key='-datetime-')]],title='현재 시간')],
                       [psg.Frame(layout=[[psg.Text('제작자: 조성진',size=(18,1), justification='center',font=('Helvetica', 15, 'bold'))],
                                          [psg.Text('문 의: dldlftktka@naver.com',size=(25,1), justification='center',font=('Helvetica', 12, 'bold'))],
                                           ],title='정보')]],element_justification='c',vertical_alignment='t',border_width=0,pad=(0,0))],
              [psg.Frame(layout=[[psg.Input(size=(60,1),change_submits=True,key='-File-',disabled=True),
                                  psg.FileBrowse(size=(10,1),file_types=(('xlsx File','*.xlsx'),))]],title='파일 찾기')],
              [psg.Frame(layout=[[psg.Multiline(autoscroll=True,size=(70,20),key='-CommandLine-',rstrip=False)]],title='명령어 입력'),
               psg.Frame('',[
                   [psg.Frame('',[
                       [psg.Frame(layout=[[psg.Text(size=(1,1))]],title='',border_width=0)],
                       [psg.Frame(layout=[[psg.Text(size=(1,1))]],title='',border_width=0)]],element_justification='c',border_width=0,pad=(0,50))],
                   [psg.Frame(layout=[[psg.Ok(size=(10,5)),psg.Exit(size=(10,5))]],title='',border_width=0,vertical_alignment='c')]]
                         ,border_width=0,vertical_alignment='b',element_justification='c',pad=(30,10))]]
   
    window = psg.Window('SSH 자동화 Tool v0.1 (paramiko)', layout, grab_anywhere=True, finalize=True)      # make gui window
   
    while True:
        event, values = window.read(timeout=900)   # Read the event that happened and the values dictionary
        window['-datetime-'].update(datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')) # time update
       
        if event in (None, 'Exit', 'Quit'):     # If user closeddow with X or if user clicked "Exit" button then exit
            break
        if values['-Only_T-']:      # Tacacs 계정만 사용
            window['-Only_T-'].update(circle_color='yellow', text_color='yellow')
            window['-Only_L-'].update(circle_color='black', text_color='white')
            window['-T_W_L-'].update(circle_color='black', text_color='white')
            window['-TacacsID-'].update(disabled=False)
            window['-TacacsPW-'].update(disabled=False)
            window['-LocalID-'].update('',disabled=True)
            window['-LocalPW-'].update('',disabled=True)
           
        if values['-Only_L-']:      # Local 계정만 사용
            window['-Only_T-'].update(circle_color='black', text_color='white')
            window['-Only_L-'].update(circle_color='yellow', text_color='yellow')
            window['-T_W_L-'].update(circle_color='black', text_color='white')
            window['-TacacsID-'].update('',disabled=True)
            window['-TacacsPW-'].update('',disabled=True)
            window['-LocalID-'].update(disabled=False)
            window['-LocalPW-'].update(disabled=False)
           
        if values['-T_W_L-']:       # Tacacs 계정 및 Local 계정 사용
            window['-Only_T-'].update(circle_color='black', text_color='white')
            window['-Only_L-'].update(circle_color='black', text_color='white')
            window['-T_W_L-'].update(circle_color='yellow', text_color='yellow')
            window['-TacacsID-'].update(disabled=False)
            window['-TacacsPW-'].update(disabled=False)
            window['-LocalID-'].update(disabled=False)
            window['-LocalPW-'].update(disabled=False)
           
        if event == 'Ok':
            Account_Mode = bool(values['-T_W_L-'])      # 계정 모드 변수
           
            pop_ch = psg.PopupOKCancel("","                 진행하시겠습니까?                 \x00", "                 중지 할 수 없습니다.                 \x00", "", title='확인')        # popup 공백 넣는 기술.. 1차원 배열이라 "","",""... 이런식임
            if pop_ch == 'OK':
                if values['-Port-'].isdigit():
                    try:    
                        DEVICES, DEVICES_NAME, LOG_FILE_PATH, USER_TAC, PASSWD_TAC, USER_LOCAL, PASSWD_LOCAL, PORT, COMMAND = Run()
                   
                        Error_File_Path = "%s\Error"%(LOG_FILE_PATH)
                        os.mkdir(Error_File_Path)
                       
                        start = time.time()

                        window.hide() # 창 숨기기
                       
                        err_div = Processing_Window()
                       
                        if err_div.empty():
                            os.rmdir(Error_File_Path)
                        else:
                            error_log = open('%s\error_ip.txt'%Error_File_Path, "a+")
                            for i in range(err_div.qsize()):
                                error_log.write("%s\n"%err_div.get())
                            error_log.close()
                       
                       
                       
                        window.un_hide() # 창 보이기
                        '''
                        try:
                            os.rmdir(Error_File_Path)
                        except:
                            pass
                        '''
                        end = (time.time() - start)
                        sec_result = str(datetime.timedelta(seconds=end)).split(".")
                       
                        psg.popup("", "          완료          \x00", "          걸린 시간: %s(초)          \x00"%sec_result[0], "",title='완료', font=(20), keep_on_top = True)
                       
                    except Exception as e:
                        psg.popup("", "          정보를 다시 확인해주세요.          \x00", "%s"%e,title='정보')
                else:
                    psg.popup("", "          포트에는 숫자만 입력가능합니다.          \x00", "",title='정보')
            else:
                psg.popup("", "          취소하였습니다.          \x00", "",title='정보')
                   
               
                           
    window.close()
    sys.exit()