py.lib

Yohn Y. 2019-10-01 Child:1668cc57225b

10:af2bf518950a Go to Latest

py.lib/ssh_tools.py

+ Модуль - сосуд всяких идей по рабоче с SSH. Вообще нужно разобрать его

History
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/ssh_tools.py	Tue Oct 01 23:14:52 2019 +0300
     1.3 @@ -0,0 +1,397 @@
     1.4 +# -*- coding: utf-8 -*-
     1.5 +import paramiko
     1.6 +from datetime import datetime
     1.7 +
     1.8 +# --- OPTS --- #
     1.9 +TIMEOUT = 120
    1.10 +
    1.11 +# --- CONST --- #
    1.12 +# Политика ключей узла
    1.13 +HOSTKEY_autoAdd = 0
    1.14 +HOSTKEY_warn = 1
    1.15 +HOSTKEY_reject = 2
    1.16 +
    1.17 +def promptChecker(user, host, buf):
    1.18 +	Signs = ['[', ']', '#', '<', '>', '$', '!', '%', '(', ')', '@', '+', ':', ';', ' ', '\t']
    1.19 +	isPrompt = False
    1.20 +	isSep = True
    1.21 +	newLites = 0
    1.22 +	endBuf = ''
    1.23 +	for i in range(1, len(buf) + 1):
    1.24 +		if not isPrompt:
    1.25 +			if buf[-i] == '\n':
    1.26 +				endBuf = buf[-i] + endBuf
    1.27 +				continue
    1.28 +			isPrompt = True
    1.29 +		if isSep:
    1.30 +			if buf[-i] in Signs:
    1.31 +				endBuf = buf[-i] + endBuf
    1.32 +				continue
    1.33 +			isSep = False
    1.34 +		if buf[-i] == '\n':
    1.35 +			break
    1.36 +	_startProc = len(buf) - i
    1.37 +	_endProc = len(buf) - len(endBuf)
    1.38 +	buf = buf[_startProc:_endProc]
    1.39 +	searchCond = []
    1.40 +	for i in [user, host]:
    1.41 +		bufL = ''
    1.42 +		bufR = ''
    1.43 +		index = buf.find(i)
    1.44 +		if index != -1:
    1.45 +			for ch in buf[:index][::-1]:
    1.46 +				if ch in Signs:
    1.47 +					bufL = ch + bufL
    1.48 +				else:
    1.49 +					break
    1.50 +			index = index + len(i)
    1.51 +			for ch in buf[index:]:
    1.52 +				if ch in Signs:
    1.53 +					bufR += ch
    1.54 +				else:
    1.55 +					break
    1.56 +			searchCond.append(bufL + i + bufR)
    1.57 +	
    1.58 +	def Check(buf):
    1.59 +		if buf.endswith(endBuf):
    1.60 +			endSearch = len(buf) - len(endBuf)
    1.61 +			startSearch = buf.rfind('\n', 0, len(buf) - len(endBuf))
    1.62 +			
    1.63 +			if startSearch == -1: startSearch = 0
    1.64 +			
    1.65 +			_buf = buf[startSearch:endSearch]
    1.66 +			for i in searchCond:
    1.67 +				if _buf.find(i) == -1: return False
    1.68 +			return True
    1.69 +		else:
    1.70 +			return False
    1.71 +	return Check		
    1.72 +		
    1.73 +
    1.74 +class SSHError(Exception): pass
    1.75 +
    1.76 +class TOCheck(object):
    1.77 +	"""
    1.78 +	Взято из: https://bitbucket.org/awgur/pylib/src/default/timetools.py	
    1.79 +	"""
    1.80 +	def __init__(self, timeOut):
    1.81 +		self.start = datetime.now()
    1.82 +		self.timeOut = timeOut
    1.83 +		                           
    1.84 +	def __call__(self, timeOut=None):
    1.85 +		if not timeOut:
    1.86 +			timeOut = self.timeOut
    1.87 +			
    1.88 +			buf = datetime.now() - self.start
    1.89 +			
    1.90 +            if buf.seconds > timeOut:
    1.91 +				return True
    1.92 +			else:
    1.93 +				return False
    1.94 +				
    1.95 +	def __bool__(self):
    1.96 +		return not self():
    1.97 +
    1.98 +
    1.99 +class SSHSubsh(object):
   1.100 +	"""Класс для облегчения работы с контекстами, когда приглашение коммандной оболочки 
   1.101 +	меняется в результате исполнения комманды.
   1.102 +	"""
   1.103 +	def __init__(self, ssh, prompt):
   1.104 +		self.ssh = ssh
   1.105 +		self.prompt = SSHPromptCheck(prompt)
   1.106 +		
   1.107 +	def __call__(self, cmd):
   1.108 +		res = self.ssh(cmd, wait=False)
   1.109 +		res += self.waitPrompt(self.prompt)
   1.110 +		return res
   1.111 +
   1.112 +class SSHPromptCheck(object):
   1.113 +	"""
   1.114 +	Проверяет, появилось ли приглашение коммандной строки в выводе сервера
   1.115 +	
   1.116 +	Принимает:
   1.117 +	  - prompt:
   1.118 +	      Строка, которая должна однозначно идентифицировать приглашение 
   1.119 +	      коммандной строки, либо функция которой передаётся строка, и она 
   1.120 +	      должна вернуть True, если строка завершается приглашением коммандной
   1.121 +	      строки либо False в обратном случае. Кроме того можно передат сам объект,
   1.122 +	      созданный заранее.
   1.123 +	"""
   1.124 +	def __init__(self, prompt):
   1.125 +		if isinstance(prompt, self.__class__):
   1.126 +			self.prompt = prompt.prompt
   1.127 +			self.check = prompt.check
   1.128 +		else:
   1.129 +			def isFunc(): pass
   1.130 +			if type(prompt) == type(isFunc):
   1.131 +				self.check = prompt
   1.132 +				self.prompt = None
   1.133 +			else:
   1.134 +				self.prompt = prompt
   1.135 +			
   1.136 +			if self.prompt == None:
   1.137 +				raise SSHError('Prompt not set')
   1.138 +			
   1.139 +		
   1.140 +	def check(self, buf):
   1.141 +		if buf.endswith(self.prompt):
   1.142 +			return True
   1.143 +		else:
   1.144 +			return False
   1.145 +	
   1.146 +	def __call__(self, buf):
   1.147 +		if isinstance(buf, SSHOutput):
   1.148 +			return self.check(buf.out)
   1.149 +		elif buf:
   1.150 +			return self.check(buf)
   1.151 +		else:
   1.152 +			return False
   1.153 +			
   1.154 +		
   1.155 +class SSHOutput(object):
   1.156 +	"""Содержит вывод удалённого сервера и помогает управляться с ним.
   1.157 +	"""
   1.158 +	def __init__(self, out = '', err = ''):
   1.159 +		self.out = out
   1.160 +		self.err = err
   1.161 +		
   1.162 +	
   1.163 +	def error(self, buf):
   1.164 +		"Добавить буфер к буферу сообщения об ошибке"
   1.165 +		self.err += buf
   1.166 +		
   1.167 +	def __add__(self, buf):
   1.168 +		if isinstance(buf, self.__class__):
   1.169 +			self.out += buf.out
   1.170 +			self.err += buf.err
   1.171 +		else:	
   1.172 +			self.out += buf
   1.173 +		return self
   1.174 +		
   1.175 +	def __str__(self):
   1.176 +		return self.out
   1.177 +	
   1.178 +	def getError(self):
   1.179 +		return self.err
   1.180 +	
   1.181 +	def __bool__(self):
   1.182 +		if self.out or self.err:
   1.183 +			return True
   1.184 +		else:
   1.185 +			return False
   1.186 +	
   1.187 +	def __repr__(self):
   1.188 +		buf = ''
   1.189 +		buf += '---- OUTPUT -----------'
   1.190 +		buf += self.out
   1.191 +		buf += '---- ERRORS -----------'
   1.192 +		buf += self.err
   1.193 +		return buf
   1.194 +		
   1.195 +	def __getitem__(self, key):
   1.196 +		return self.out.splittines()[key]
   1.197 +		
   1.198 +	def __iter__(self):
   1.199 +		for line in self.out.splitlines():
   1.200 +			yield line
   1.201 +	
   1.202 +	def isEol(self):
   1.203 +		if self.out[-1] == '\n':
   1.204 +			return True
   1.205 +		else:
   1.206 +			return False
   1.207 +			
   1.208 +			
   1.209 +class SSHCommunicate(object):
   1.210 +	"""Взаимодействие с командой ssh. В сосотоянии отправлять ей данные и принимать данные от неё.
   1.211 +	
   1.212 +	Обеспчивает работу с exec_command в SSH
   1.213 +	"""
   1.214 +	def __init__(self, stdin, stdout, stderr):
   1.215 +		self.inC = stdin
   1.216 +		self.out = stdout
   1.217 +		self.err = stderr
   1.218 +		
   1.219 +	def __call__(self, buf=None):
   1.220 +		if buf != None:
   1.221 +			self.inC.write(buf)
   1.222 +		return SSHOutput(self.out.read(), self.err.read())
   1.223 +	
   1.224 +	def __str__(self):
   1.225 +		return str(self())
   1.226 +		
   1.227 +	def __repr__(self):
   1.228 +		return repr(self())
   1.229 +
   1.230 +class _SSH(object):
   1.231 +	"""Метакласс для SSH соединений
   1.232 +	
   1.233 +	Принимает:
   1.234 +	  - host: Имя узла
   1.235 +	  - user: Имя пользователя
   1.236 +	  - passwd(не обязательно, если есть key): пароль пользователя
   1.237 +	  - key(не обязательно если есть passwd): файл ключа
   1.238 +	  - hostKeyPol[HOSTKEY_autoAdd | HOSTKEY_warn | HOSTKEY_reject]:
   1.239 +	      HOSTKEY_autoAdd(По умолчанию): 
   1.240 +	        если ключ удалённого узла нам неизвестен, добавить, соединение разрешить.
   1.241 +	      HOSTKEY_warn: если ключ удалённого узла нам неизвестен, предупредить.
   1.242 +	      HOSTKEY_reject: если ключ удалённого узла нам неизвестен, разорвать соединение.
   1.243 +	  - timeout: таймаут операций с сервером.
   1.244 +	"""
   1.245 +	def __init__(self, host, user, passwd=None, key=None, 
   1.246 +			hostKeyPol=HOSTKEY_autoAdd, timeout=None
   1.247 +		):
   1.248 +		if passwd == None and key == None:
   1.249 +			raise SSHError('Auth type not set')
   1.250 +				
   1.251 +		if hostKeyPol == HOSTKEY_autoAdd:
   1.252 +			_hostKeyPol = paramiko.AutoAddPolicy()
   1.253 +		elif hostKeyPol == HOSTKEY_warn:
   1.254 +			_hostKeyPol = paramiko.WarningPolicy()
   1.255 +		elif hostKeyPol == HOSTKEY_reject:
   1.256 +			_hostKeyPol = paramiko.RejectPolicy()
   1.257 +		else:
   1.258 +			raise SSHError('Unknown policy type')
   1.259 +		
   1.260 +		self.ssh = paramiko.SSHClient()	
   1.261 +		self.ssh.set_missing_host_key_policy(_hostKeyPol)	
   1.262 +		if passwd:
   1.263 +			self.ssh.connect(hostname=host, username=user, password=passwd, look_for_keys=False, allow_agent=False)
   1.264 +		else:
   1.265 +			self.ssh.connect(hostname=host, username=user, key_filename=key, look_for_keys=False, allow_agent=False)
   1.266 +			
   1.267 +		if timeout != None:
   1.268 +			self.timeout = timeout
   1.269 +		else:
   1.270 +			self.timeout = TIMEOUT
   1.271 +		
   1.272 +		self.user = user
   1.273 +		self.host = host
   1.274 +	
   1.275 +	def __del__(self):
   1.276 +		try:
   1.277 +			self.ssh.close()
   1.278 +		except:
   1.279 +			pass
   1.280 +		del self.ssh		
   1.281 +		
   1.282 +class InterSSH(_SSH):
   1.283 +	"""Класс Взаимодействия с сервером SSH
   1.284 +	
   1.285 +	Кроме аргументов необходимых для метакласса принимает:
   1.286 +	  - prompt: 
   1.287 +	      Параметр конструктора SSHPromptCheck. Принимается строго по имени.
   1.288 +	"""
   1.289 +	def __init__(self, *a, **kwa):
   1.290 +		if 'prompt' in kwa:
   1.291 +			prompt = kwa['prompt']
   1.292 +			del kwa['prompt']
   1.293 +			
   1.294 +		_SSH.__init__(self, *a, **kwa)
   1.295 +		
   1.296 +		self.shell = self.ssh.invoke_shell()
   1.297 +		self.shell.settimeout(self.timeout)
   1.298 +		if prompt == None:
   1.299 +			prompt = self._getPrompt()
   1.300 +			self.ready = True
   1.301 +		else:
   1.302 +			self.ready = False
   1.303 +		self.prompt = SSHPromptCheck(prompt)
   1.304 +
   1.305 +		
   1.306 +	def __bool__(self):
   1.307 +		"Готово ли соединение принимать новые комманды"
   1.308 +		return self.ready
   1.309 +	
   1.310 +	def _getPrompt(self):
   1.311 +		"""Возвращает объект проверки на приглашение комммандной строки из текщего
   1.312 +		буфера вывода.
   1.313 +		"""
   1.314 +		buf = ''
   1.315 +		while self.shell.recv_ready():
   1.316 +			buf += self.shell.recv(1024)
   1.317 +		
   1.318 +		func = promptChecker(self.user, self.host, buf)
   1.319 +		return SSHPromptCheck(func)
   1.320 +
   1.321 +	
   1.322 +	def waitPrompt(self, prompt=None):
   1.323 +		"""Ожидание приглашения коммандной строки
   1.324 +		
   1.325 +		Принимает необязательный аргумент: Экземпляр класса SSHPromptCheck, с помощью котороого 
   1.326 +		проверяется присутствие приглашения командного интерпретатора в выводе.
   1.327 +		"""
   1.328 +		if prompt == None:
   1.329 +			prompt = self.prompt
   1.330 +		else:
   1.331 +			prompt = SSHPromptCheck(prompt)
   1.332 +			
   1.333 +		if not self.ready:
   1.334 +			timeCheck = TOCheck(self.timeout)
   1.335 +			res = SSHOutput()
   1.336 +			while True:
   1.337 +				while self.shell.recv_stderr_ready():
   1.338 +					res.error(self.shell.recv_stderr(1024))
   1.339 +
   1.340 +				while self.shell.recv_ready():
   1.341 +					res += self.shell.recv(1024)
   1.342 +
   1.343 +				if prompt(res):
   1.344 +					break
   1.345 +		
   1.346 +				if timeCheck():
   1.347 +					raise SSHError('Command call timeout\n' + repr(res))
   1.348 +		
   1.349 +		self.ready = True
   1.350 +		return res
   1.351 +
   1.352 +	def __call__(self, cmd, wait=True):
   1.353 +		"""Запуск команды
   1.354 +		
   1.355 +		Принимает:
   1.356 +		  - cmd: Комманда
   1.357 +		  - wait(необязательный аргумент): Признак ожидания завершения комманды.
   1.358 +		  
   1.359 +		Возвращает: Экземпляр класса SSHOutput
   1.360 +		"""
   1.361 +		res = SSHOutput()
   1.362 +		if not self.ready:
   1.363 +			res += self.waitPrompt()
   1.364 +		bytes = self.shell.send('%s\n' % cmd)
   1.365 +		if bytes == 0:
   1.366 +			raise SSHError('Channel closed')
   1.367 +		if wait:
   1.368 +			res += self.waitPrompt()
   1.369 +		else:
   1.370 +			self.ready = False
   1.371 +		return res
   1.372 +
   1.373 +	def subShell(self, cmd, prompt=None):
   1.374 +		"""Возвращает объект SSHSubsh, через который удобно работать 
   1.375 +		интерпретаторами, запускаемыми из текущего, либо с ситуацией изменения приглашения 
   1.376 +		командного интерпретатора. 
   1.377 +		"""
   1.378 +		self(cmd, wait=False)
   1.379 +		if prompt == None:
   1.380 +			prompt = self._getPropt()
   1.381 +		return SSHSubsh(self, prompt)
   1.382 +			
   1.383 +	def close(self):
   1.384 +		res = SSHOutput
   1.385 +		if not self:
   1.386 +			res += self.waitPrompt()
   1.387 +		self.ssh.close()
   1.388 +		return res
   1.389 +
   1.390 +class SSH(_SSH):
   1.391 +	"""Класс призванный управлять выполнением единичных комманд, без привлечения контекста.
   1.392 +	
   1.393 +	Принимает аргументы необходимые для метакласса _SSH
   1.394 +	"""
   1.395 +	def __init__(self, *a, **kwa):
   1.396 +		_SSH.__init__(self, *a, **kwa)
   1.397 +		
   1.398 +	def __call__(self, cmd):
   1.399 +		return SSHCommunicate(*self.ssh.exec_command(cmd, timeout=self.timeout))
   1.400 +		
   1.401 \ No newline at end of file