## Module to manage connections with remote.
import os
import errno
from stat import S_ISDIR
import paramiko
[docs]def splitall(path):
"""https://www.oreilly.com/library/view/python-cookbook/0596001673/ch04s16.html
"""
allparts = []
while 1:
parts = os.path.split(path)
if parts[0] == path: # sentinel for absolute paths
allparts.insert(0, parts[0])
break
elif parts[1] == path: # sentinel for relative paths
allparts.insert(0, parts[1])
break
else:
path = parts[0]
allparts.insert(0, parts[1])
return allparts
[docs]class SSH(object):
def __init__(self,hostname,hostuser,keypath):
self.hostname = hostname
self.hostuser = hostuser
self.keypath = keypath
self.client = paramiko.SSHClient()
self.client.load_host_keys(keypath)
self.client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
[docs]class SSHConnection(SSH):
"""Context Manager for paramiko managed ssh clients. From https://extsoft.pro/safely-destroying-connections-in-python/
"""
def __enter__(self):
"""Enter the runtime context for this object.
"""
self.client.connect(self.hostname,22,self.hostuser)
return self
def __exit__(self, exc_type, exc_val, exc_tb):
"""Exit the runtime context and close connection.
"""
if self.client:
self.client.close()
[docs] def exec_command(self,command):
"""Direct map to SSHClient.exec_command
"""
return self.client.exec_command(command)
[docs]class FTPConnection(SSH):
"""Context Manager for file transfer.
"""
def __enter__(self):
"""Enter the runtime context and start sftp client.
"""
self.client.connect(self.hostname,22,self.hostuser)
self.ftp_client = self.client.open_sftp()
return self
def __exit__(self, exc_type, exc_val, exc_tb):
"""Exit the runtime context and close connection.
"""
try:
if self.ftp_client:
self.ftp_client.close()
except:
pass
try:
if self.client:
self.client.close()
except:
pass
[docs] def get(self,remotepath,localpath):
"""Directly maps to paramiko.sftp_client.SFCTClient.get()
:param remotepath: path to the remote file we want to get.
:param localpath: location we want to write to locally.
"""
self.ftp_client.get(remotepath,localpath)
[docs] def put(self,localpath,remotepath):
"""Directly maps to paramiko.sftp_client.SFCTClient.put()
:param localpath: path to the local file we want to put.
:param remotepath: location we want to write to remotely.
"""
self.ftp_client.put(localpath,remotepath)
[docs] def exists(self,filepath):
"""Like the os.path.exists command through paramiko's SFTP client. See https://stackoverflow.com/questions/850749/check-whether-a-path-exists-on-a-remote-host-using-paramiko
"""
try:
self.ftp_client.stat(filepath)
except IOError as e:
if e.errno == errno.ENOENT:
return False
raise
else:
return True
[docs] def isdir(self,dirpath):
"""Checks if the given path is a directory:
https://stackoverflow.com/questions/20507055/recursive-remove-directory-using-sftp/20507586#20507586
:param dirpath:
"""
try:
return S_ISDIR(self.ftp_client.stat(dirpath).st_mode)
except IOError:
return False
[docs] def rm(self,path):
"""Recursive removal of directory.
:param path:
"""
files = self.ftp_client.listdir(path = path)
for f in files:
filepath = os.path.join(path,f)
if self.isdir(filepath):
self.rm(filepath)
else:
self.ftp_client.remove(filepath)
self.ftp_client.rmdir(path)
[docs] def mkdir(self,dirpath):
"""Directly maps to paramiko.sftp_client.SFPTClient.mkdir()
:param dirpath: requested path (must be absolute)
"""
self.ftp_client.mkdir(dirpath)
[docs] def mkdir_notexists(self,dirpath):
"""Make directory only if it does not exist.
:param dirpath: requested path (must be absolute)
"""
if not self.exists(dirpath):
self.mkdir(dirpath)
else:
pass
[docs] def mkdir_r_notexists(self,dirpath):
"""Make a nested directory, creating new subdirectories as necessary.
NOTE: Will not check if dirpath is a filepath. if it is, you might have overwrite issues.
:param dirpath: requested path (must be absolute)
"""
path_parts = splitall(dirpath)
for i in range(len(path_parts)):
self.mkdir_notexists(os.path.join(*path_parts[:i+2]))
[docs] def r_put(self,localpath,remotepath):
"""When given a local directory, recursively puts contents of localpath at remotepath.
:param localpath: path to the local directory we want to put.
:param remotepath: location we want to write to remotely.
"""
## First make the remote location:
self.mkdir_r_notexists(remotepath)
## Now recursive put:
gen = os.walk(localpath,topdown = True)
for p,dirs,files in gen:
relpath = os.path.relpath(p,localpath)
if len(dirs) > 0: ## if there are directories, see if they exist at remote:
for d in dirs:
remotedir = os.path.join(remotepath,relpath,d)
self.mkdir_notexists(remotedir)
if len(files) > 0: ## if there are files, copy them:
for f in files:
localfile = os.path.join(p,f)
remotefile = os.path.join(remotepath,relpath,f)
self.put(localfile,remotefile)
[docs] def r_get(self,remotepath,localpath):
"""When given a remote directory, recursively puts contents of remotepath at localpath.
:param remotepath: path to the remote directory we want to get from.
:param localpath: location we want to write to locally.
"""
## First create local location:
os.makedirs(localpath,exist_ok = True)
## Now recursive put:
remotedir = remotepath
localdir = localpath
files = self.ftp_client.listdir(path = remotepath)
for f in files:
remoteloc = os.path.join(remotedir,f)
localloc = os.path.join(localdir,f)
if self.isdir(remoteloc):
self.r_get(remoteloc,localloc)
else:
self.get(remoteloc,localloc)