본문 바로가기

아이티 :D/Python

파이썬 Paramiko 모듈을 이용한 커스텀 SFTP모듈 만들기!


import os
import sys
import paramiko
import threading
import time

'''
사용중인 logger가 있으시다면 아래 logger 변수에 할당해주시기 바랍니다.
만약 없다면 applogger의 Logger를 디폴트로 사용합니다.
'''
logger = None
_loggerflag = False

if logger is None:
from applogger import Logger
_loggerflag = True
logger = Logger(logtype='STREAM',loglevel='DEBUG').UseLogger()

'''
테스트를 위한 사용자정보 딕셔너리
(추후 다른 모듈과 연동될때는 DB든 JSON이든 사용자 정보를 아래와 같이
딕셔너리 형태로 받아옵니다.)
'''
info = {
0:{
'hostname':'', # 스트링
'port': 22, # 인트
'username': '', # 스트링
'password': '', # 스트링
'src': [''], # 리스트
'dst': [''] # 리스트
},
1:{
'hostname':'', # 스트링
'port': 22, # 인트
'username': '', # 스트링
'password': '', # 스트링
'src': [''], # 리스트
'dst': [''] # 리스트
}

}


class UseSFTP(threading.Thread):

def __init__(self, hostname, port, username, password, src, dst):
threading.Thread.__init__(self)
self.hostname = hostname
self.port = port
self.username = username
self.password = password
self.src = src
self.dst = dst


def ConnectSFTP(self):
'''
원격지 SSH와 SFTP 클라이언트를 반환하는 함수
'''
ssh = None
sftp = None
try:
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh.load_host_keys(os.path.expanduser(os.path.join("~", ".ssh", "known_hosts")))
ssh.connect(hostname=self.hostname, port=self.port, username=self.username, password=self.password)
sftp = ssh.open_sftp()
except Exception as e:
if _loggerflag is True:
logger.warning('{}'.format(e))
else:
print(e)
ssh = None
sftp = None
return ssh, sftp


def GetFileSize(self,file):
'''
파일 사이즈 출력해주는 함수
'''
if file < 1024:
     return str(file) + "byte"
elif file < 1024*1024:
return str(file/1024) + "kbyte"
elif file < 1024*1024*1024:
return str(file/1024/1024) + "mbyte"


def PrintSummury(self,hostname,totaltime,filesize,filename):
'''
전송 정보 요약해서 출력해주는 함수, _loggerflag가 True일 경우에만 사용됩니다.
'''
if len(filename) > 1:
for i in range(len(filename)):
logger.info('{} Transmission file: {}'.format(hostname,filename[i]))
else:
logger.info('{} Transmission file: {}'.format(hostname,filename))
logger.info('{} Transmission size: {}'.format(hostname,filesize))
logger.info('{} Transmission time: {} seconds'.format(hostname,totaltime))
logger.info('{} Transmission Complete'.format(hostname))


def TransferDir(self):
'''
로컬에서 원격지로 디렉토리 전송을 담당하는 함수
'''
try:
error = ''
flag = True
# 소스경로
self.src[0] = os.path.abspath(self.src[0]) + '/'
parent = os.path.expanduser(self.src[0])
ssh, sftp = self.ConnectSFTP()
totaltime = ''
filesize = 0
totalsize = 0
fileindex = self.src[0][:self.src[0].rfind('/')].rfind('/')
srcdir = self.src[0][fileindex:self.src[0].rfind('/')]
# 배포경로
self.dst[0] = os.path.abspath(self.dst[0]) + srcdir
# 배포경로 생성
stdin, stdout, stderr = ssh.exec_command('mkdir -p %s' % self.dst[0])
# exec_command가 non-blocking이라 blocking으로 ssh.exec_command의 결과를 대기
exit_status = stderr.channel.recv_exit_status()
# ssh.exec_command명령에서 에러가 발생한 경우에 진입
if exit_status is not 0:
flag = False
error = 'exit_status is 1'
return flag, error
# ssh와 sftp가 이상없이 연결되었을 경우에 진입
if ssh is not None and sftp is not None:
starttime = time.time()
for dirpath, dirnames, filenames in os.walk(parent):
remote_path = os.path.join(self.dst[0], dirpath[len(parent):])
try:
sftp.listdir(remote_path)
except IOError:
sftp.mkdir(remote_path)
except Exception as e:
if _loggerflag is True:
logger.warning('{}'.format(e))
else:
print(e)

for i in range(len(filenames)):
filesize = filesize + os.path.getsize(dirpath+'/'+filenames[i])
sftp.put(os.path.join(dirpath, filenames[i]), os.path.join(remote_path, filenames[i]))

endtime = time.time()
totaltime = '%.02f' % (endtime - starttime)
if _loggerflag is True:
self.PrintSummury(self.hostname,totaltime,self.GetFileSize(filesize),self.src)
else:
pass

else:
flag = False
error = 'SSH & SFTP CONNECTION is FAIL'
return flag, error

except Exception as e:
flag = False
error = e
return flag, error


def TrasferFiles(self):
'''
로컬에서 원격지로 파일 전송을 담당하는 함수
'''
error = ''
flag = True
# 소스경로
self.dst[0] = os.path.abspath(self.dst[0]) + '/'
# ssh & sftp 클라이언트 연결
ssh, sftp = self.ConnectSFTP()
totaltime = ''
filesize = 0
totalsize = 0
try:
# 배포경로 생성
stdin, stdout, stderr = ssh.exec_command('mkdir -p %s' % self.dst[0])
# exec_command가 non-blocking이라 blocking으로 ssh.exec_command의 결과를 대기
exit_status = stderr.channel.recv_exit_status()

# ssh.exec_command명령에서 에러가 발생한 경우에 진입
if exit_status is not 0:
flag = False
error = 'exit_status is 1'
return flag, error

# ssh와 sftp가 이상없이 연결되었을 경우에 진입
if ssh is not None and sftp is not None:
starttime = time.time()
# src 개수만큼 반복하면서 원격지에 파일 전송
for i in range(len(self.src)):
filesize = filesize + os.path.getsize(self.src[i])
if not os.path.isfile(self.src[i]):
if _loggerflag is True:
logger.info('No Such File...')
else:
print('No Such File...')
return
elif len(stderr.readlines()) == 1:
if _loggerflag is True:
logger.warning('No Such Directory...')
else:
print('No Such Directory...')
sftp.mkdir(self.dst[0], mode=511)
else:
filename_index = self.src[i].rfind('/')
filename = self.src[i][filename_index+1:]
sftp.put(self.src[i], self.dst[0]+filename)

else:
flag = False
error = 'SSH & SFTP CONNECTION is FAIL'
return flag, error

endtime = time.time()
totaltime = '%.02f' % (endtime - starttime)

if _loggerflag is True:
self.PrintSummury(self.hostname,totaltime,self.GetFileSize(filesize),self.src)
else:
pass

except IOError as e:
flag = False
error = e
return flag, error

except Exception as e:
flag = False
error = e
return flag, error


def SendToRemote(self):
'''
파일전송인지 폴더전송인지 구분한 후 전송함수 호출을 담당하는 함수
'''
# 소스가 파일 전송일 경우
if os.path.isfile(self.src[0]) or len(self.src) > 1:
self.TrasferFiles()
# 소스가 폴더 전송일 경우
elif not os.path.isfile(self.src[0]) and len(self.src) < 2:
self.TransferDir()


def run(self):
'''
스레드 Run 담당하는 함수
'''
try:
self.SendToRemote()

except Exception as e:
logger.warning('{}'.format(e))


def GetInformation(info):
'''
SFTP 전송관련 정보들을 담은 dict를 반환하는 함수
'''
infolist = []
idx = len(info)
for i in range(idx):
infolist.append(info[i])
return infolist


def Validation():

flag = True
data = GetInformation(info)
for i in range(len(data)):
if len(data[i]['hostname']) < 1:
logger.critical('%d hostname 입력되지 않았습니다' % i)
flag = False
if data[i]['port'] is None:
logger.critical('%d port 입력되지 않았습니다' % i)
flag = False
if len(data[i]['username']) < 1:
logger.critical('%d username 입력되지 않았습니다' % i)
flag = False
if len(data[i]['password']) < 1:
logger.critical('%d password 입력되지 않았습니다' % i)
flag = False
if len(data[i]['src']) < 1:
logger.critical('%d src 입력되지 않았습니다' % i)
flag = False
if len(data[i]['dst']) < 1:
logger.critical('%d dst 입력되지 않았습니다' % i)
flag = False
return flag, data
def main():
'''
배포대상 서버의 수만큼 스레드가 생성되며 스레드를 시작시키는 메인함수
self, hostname, port, username, src, dst)
'''
flag, data = Validation()


if flag is True:
for i in range(len(data)):
clss = UseSFTP(data[i]['hostname'],
data[i]['port'],
data[i]['username'],
data[i]['password'],
data[i]['src'],
data[i]['dst'])

clss.start()


if __name__ == "__main__":
'''
아래와 같이 메인함수를 호출하여 테스트 하시면 됩니다.
'''
if _loggerflag is True:
logger.info('START SFTP Module :D')
else:
print('START SFTP Module :D')
main()
if _loggerflag is True:
logger.info('END SFTP Module!!')
else:
print('END SFTP Module!!')