py.lib
2019-10-01
Child:1668cc57225b
py.lib/ssh_tools.py
+ Модуль - сосуд всяких идей по рабоче с SSH. Вообще нужно разобрать его
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