py.lib
py.lib/ssh_tools.py
+ Добавлены простые логгеры
| awgur@10 | 1 # -*- coding: utf-8 -*- |
| awgur@10 | 2 import paramiko |
| awgur@10 | 3 from datetime import datetime |
| awgur@10 | 4 |
| awgur@10 | 5 # --- OPTS --- # |
| awgur@10 | 6 TIMEOUT = 120 |
| awgur@10 | 7 |
| awgur@10 | 8 # --- CONST --- # |
| awgur@10 | 9 # Политика ключей узла |
| awgur@10 | 10 HOSTKEY_autoAdd = 0 |
| awgur@10 | 11 HOSTKEY_warn = 1 |
| awgur@10 | 12 HOSTKEY_reject = 2 |
| awgur@10 | 13 |
| awgur@10 | 14 def promptChecker(user, host, buf): |
| awgur@10 | 15 Signs = ['[', ']', '#', '<', '>', '$', '!', '%', '(', ')', '@', '+', ':', ';', ' ', '\t'] |
| awgur@10 | 16 isPrompt = False |
| awgur@10 | 17 isSep = True |
| awgur@10 | 18 newLites = 0 |
| awgur@10 | 19 endBuf = '' |
| awgur@10 | 20 for i in range(1, len(buf) + 1): |
| awgur@10 | 21 if not isPrompt: |
| awgur@10 | 22 if buf[-i] == '\n': |
| awgur@10 | 23 endBuf = buf[-i] + endBuf |
| awgur@10 | 24 continue |
| awgur@10 | 25 isPrompt = True |
| awgur@10 | 26 if isSep: |
| awgur@10 | 27 if buf[-i] in Signs: |
| awgur@10 | 28 endBuf = buf[-i] + endBuf |
| awgur@10 | 29 continue |
| awgur@10 | 30 isSep = False |
| awgur@10 | 31 if buf[-i] == '\n': |
| awgur@10 | 32 break |
| awgur@10 | 33 _startProc = len(buf) - i |
| awgur@10 | 34 _endProc = len(buf) - len(endBuf) |
| awgur@10 | 35 buf = buf[_startProc:_endProc] |
| awgur@10 | 36 searchCond = [] |
| awgur@10 | 37 for i in [user, host]: |
| awgur@10 | 38 bufL = '' |
| awgur@10 | 39 bufR = '' |
| awgur@10 | 40 index = buf.find(i) |
| awgur@10 | 41 if index != -1: |
| awgur@10 | 42 for ch in buf[:index][::-1]: |
| awgur@10 | 43 if ch in Signs: |
| awgur@10 | 44 bufL = ch + bufL |
| awgur@10 | 45 else: |
| awgur@10 | 46 break |
| awgur@10 | 47 index = index + len(i) |
| awgur@10 | 48 for ch in buf[index:]: |
| awgur@10 | 49 if ch in Signs: |
| awgur@10 | 50 bufR += ch |
| awgur@10 | 51 else: |
| awgur@10 | 52 break |
| awgur@10 | 53 searchCond.append(bufL + i + bufR) |
| awgur@10 | 54 |
| awgur@10 | 55 def Check(buf): |
| awgur@10 | 56 if buf.endswith(endBuf): |
| awgur@10 | 57 endSearch = len(buf) - len(endBuf) |
| awgur@10 | 58 startSearch = buf.rfind('\n', 0, len(buf) - len(endBuf)) |
| awgur@10 | 59 |
| awgur@10 | 60 if startSearch == -1: startSearch = 0 |
| awgur@10 | 61 |
| awgur@10 | 62 _buf = buf[startSearch:endSearch] |
| awgur@10 | 63 for i in searchCond: |
| awgur@10 | 64 if _buf.find(i) == -1: return False |
| awgur@10 | 65 return True |
| awgur@10 | 66 else: |
| awgur@10 | 67 return False |
| awgur@10 | 68 return Check |
| awgur@10 | 69 |
| awgur@10 | 70 |
| awgur@10 | 71 class SSHError(Exception): pass |
| awgur@10 | 72 |
| awgur@10 | 73 class TOCheck(object): |
| awgur@10 | 74 """ |
| awgur@10 | 75 Взято из: https://bitbucket.org/awgur/pylib/src/default/timetools.py |
| awgur@10 | 76 """ |
| awgur@10 | 77 def __init__(self, timeOut): |
| awgur@10 | 78 self.start = datetime.now() |
| awgur@10 | 79 self.timeOut = timeOut |
| awgur@10 | 80 |
| awgur@10 | 81 def __call__(self, timeOut=None): |
| awgur@10 | 82 if not timeOut: |
| awgur@10 | 83 timeOut = self.timeOut |
| awgur@10 | 84 |
| awgur@10 | 85 buf = datetime.now() - self.start |
| awgur@10 | 86 |
| awgur@10 | 87 if buf.seconds > timeOut: |
| awgur@10 | 88 return True |
| awgur@10 | 89 else: |
| awgur@10 | 90 return False |
| awgur@10 | 91 |
| awgur@10 | 92 def __bool__(self): |
| awgur@10 | 93 return not self(): |
| awgur@10 | 94 |
| awgur@10 | 95 |
| awgur@10 | 96 class SSHSubsh(object): |
| awgur@10 | 97 """Класс для облегчения работы с контекстами, когда приглашение коммандной оболочки |
| awgur@10 | 98 меняется в результате исполнения комманды. |
| awgur@10 | 99 """ |
| awgur@10 | 100 def __init__(self, ssh, prompt): |
| awgur@10 | 101 self.ssh = ssh |
| awgur@10 | 102 self.prompt = SSHPromptCheck(prompt) |
| awgur@10 | 103 |
| awgur@10 | 104 def __call__(self, cmd): |
| awgur@10 | 105 res = self.ssh(cmd, wait=False) |
| awgur@10 | 106 res += self.waitPrompt(self.prompt) |
| awgur@10 | 107 return res |
| awgur@10 | 108 |
| awgur@10 | 109 class SSHPromptCheck(object): |
| awgur@10 | 110 """ |
| awgur@10 | 111 Проверяет, появилось ли приглашение коммандной строки в выводе сервера |
| awgur@10 | 112 |
| awgur@10 | 113 Принимает: |
| awgur@10 | 114 - prompt: |
| awgur@10 | 115 Строка, которая должна однозначно идентифицировать приглашение |
| awgur@10 | 116 коммандной строки, либо функция которой передаётся строка, и она |
| awgur@10 | 117 должна вернуть True, если строка завершается приглашением коммандной |
| awgur@10 | 118 строки либо False в обратном случае. Кроме того можно передат сам объект, |
| awgur@10 | 119 созданный заранее. |
| awgur@10 | 120 """ |
| awgur@10 | 121 def __init__(self, prompt): |
| awgur@10 | 122 if isinstance(prompt, self.__class__): |
| awgur@10 | 123 self.prompt = prompt.prompt |
| awgur@10 | 124 self.check = prompt.check |
| awgur@10 | 125 else: |
| awgur@10 | 126 def isFunc(): pass |
| awgur@10 | 127 if type(prompt) == type(isFunc): |
| awgur@10 | 128 self.check = prompt |
| awgur@10 | 129 self.prompt = None |
| awgur@10 | 130 else: |
| awgur@10 | 131 self.prompt = prompt |
| awgur@10 | 132 |
| awgur@10 | 133 if self.prompt == None: |
| awgur@10 | 134 raise SSHError('Prompt not set') |
| awgur@10 | 135 |
| awgur@10 | 136 |
| awgur@10 | 137 def check(self, buf): |
| awgur@10 | 138 if buf.endswith(self.prompt): |
| awgur@10 | 139 return True |
| awgur@10 | 140 else: |
| awgur@10 | 141 return False |
| awgur@10 | 142 |
| awgur@10 | 143 def __call__(self, buf): |
| awgur@10 | 144 if isinstance(buf, SSHOutput): |
| awgur@10 | 145 return self.check(buf.out) |
| awgur@10 | 146 elif buf: |
| awgur@10 | 147 return self.check(buf) |
| awgur@10 | 148 else: |
| awgur@10 | 149 return False |
| awgur@10 | 150 |
| awgur@10 | 151 |
| awgur@10 | 152 class SSHOutput(object): |
| awgur@10 | 153 """Содержит вывод удалённого сервера и помогает управляться с ним. |
| awgur@10 | 154 """ |
| awgur@10 | 155 def __init__(self, out = '', err = ''): |
| awgur@10 | 156 self.out = out |
| awgur@10 | 157 self.err = err |
| awgur@10 | 158 |
| awgur@10 | 159 |
| awgur@10 | 160 def error(self, buf): |
| awgur@10 | 161 "Добавить буфер к буферу сообщения об ошибке" |
| awgur@10 | 162 self.err += buf |
| awgur@10 | 163 |
| awgur@10 | 164 def __add__(self, buf): |
| awgur@10 | 165 if isinstance(buf, self.__class__): |
| awgur@10 | 166 self.out += buf.out |
| awgur@10 | 167 self.err += buf.err |
| awgur@10 | 168 else: |
| awgur@10 | 169 self.out += buf |
| awgur@10 | 170 return self |
| awgur@10 | 171 |
| awgur@10 | 172 def __str__(self): |
| awgur@10 | 173 return self.out |
| awgur@10 | 174 |
| awgur@10 | 175 def getError(self): |
| awgur@10 | 176 return self.err |
| awgur@10 | 177 |
| awgur@10 | 178 def __bool__(self): |
| awgur@10 | 179 if self.out or self.err: |
| awgur@10 | 180 return True |
| awgur@10 | 181 else: |
| awgur@10 | 182 return False |
| awgur@10 | 183 |
| awgur@10 | 184 def __repr__(self): |
| awgur@10 | 185 buf = '' |
| awgur@10 | 186 buf += '---- OUTPUT -----------' |
| awgur@10 | 187 buf += self.out |
| awgur@10 | 188 buf += '---- ERRORS -----------' |
| awgur@10 | 189 buf += self.err |
| awgur@10 | 190 return buf |
| awgur@10 | 191 |
| awgur@10 | 192 def __getitem__(self, key): |
| awgur@10 | 193 return self.out.splittines()[key] |
| awgur@10 | 194 |
| awgur@10 | 195 def __iter__(self): |
| awgur@10 | 196 for line in self.out.splitlines(): |
| awgur@10 | 197 yield line |
| awgur@10 | 198 |
| awgur@10 | 199 def isEol(self): |
| awgur@10 | 200 if self.out[-1] == '\n': |
| awgur@10 | 201 return True |
| awgur@10 | 202 else: |
| awgur@10 | 203 return False |
| awgur@10 | 204 |
| awgur@10 | 205 |
| awgur@10 | 206 class SSHCommunicate(object): |
| awgur@10 | 207 """Взаимодействие с командой ssh. В сосотоянии отправлять ей данные и принимать данные от неё. |
| awgur@10 | 208 |
| awgur@10 | 209 Обеспчивает работу с exec_command в SSH |
| awgur@10 | 210 """ |
| awgur@10 | 211 def __init__(self, stdin, stdout, stderr): |
| awgur@10 | 212 self.inC = stdin |
| awgur@10 | 213 self.out = stdout |
| awgur@10 | 214 self.err = stderr |
| awgur@10 | 215 |
| awgur@10 | 216 def __call__(self, buf=None): |
| awgur@10 | 217 if buf != None: |
| awgur@10 | 218 self.inC.write(buf) |
| awgur@10 | 219 return SSHOutput(self.out.read(), self.err.read()) |
| awgur@10 | 220 |
| awgur@10 | 221 def __str__(self): |
| awgur@10 | 222 return str(self()) |
| awgur@10 | 223 |
| awgur@10 | 224 def __repr__(self): |
| awgur@10 | 225 return repr(self()) |
| awgur@10 | 226 |
| awgur@10 | 227 class _SSH(object): |
| awgur@10 | 228 """Метакласс для SSH соединений |
| awgur@10 | 229 |
| awgur@10 | 230 Принимает: |
| awgur@10 | 231 - host: Имя узла |
| awgur@10 | 232 - user: Имя пользователя |
| awgur@10 | 233 - passwd(не обязательно, если есть key): пароль пользователя |
| awgur@10 | 234 - key(не обязательно если есть passwd): файл ключа |
| awgur@10 | 235 - hostKeyPol[HOSTKEY_autoAdd | HOSTKEY_warn | HOSTKEY_reject]: |
| awgur@10 | 236 HOSTKEY_autoAdd(По умолчанию): |
| awgur@10 | 237 если ключ удалённого узла нам неизвестен, добавить, соединение разрешить. |
| awgur@10 | 238 HOSTKEY_warn: если ключ удалённого узла нам неизвестен, предупредить. |
| awgur@10 | 239 HOSTKEY_reject: если ключ удалённого узла нам неизвестен, разорвать соединение. |
| awgur@10 | 240 - timeout: таймаут операций с сервером. |
| awgur@10 | 241 """ |
| awgur@10 | 242 def __init__(self, host, user, passwd=None, key=None, |
| awgur@10 | 243 hostKeyPol=HOSTKEY_autoAdd, timeout=None |
| awgur@10 | 244 ): |
| awgur@10 | 245 if passwd == None and key == None: |
| awgur@10 | 246 raise SSHError('Auth type not set') |
| awgur@10 | 247 |
| awgur@10 | 248 if hostKeyPol == HOSTKEY_autoAdd: |
| awgur@10 | 249 _hostKeyPol = paramiko.AutoAddPolicy() |
| awgur@10 | 250 elif hostKeyPol == HOSTKEY_warn: |
| awgur@10 | 251 _hostKeyPol = paramiko.WarningPolicy() |
| awgur@10 | 252 elif hostKeyPol == HOSTKEY_reject: |
| awgur@10 | 253 _hostKeyPol = paramiko.RejectPolicy() |
| awgur@10 | 254 else: |
| awgur@10 | 255 raise SSHError('Unknown policy type') |
| awgur@10 | 256 |
| awgur@10 | 257 self.ssh = paramiko.SSHClient() |
| awgur@10 | 258 self.ssh.set_missing_host_key_policy(_hostKeyPol) |
| awgur@10 | 259 if passwd: |
| awgur@10 | 260 self.ssh.connect(hostname=host, username=user, password=passwd, look_for_keys=False, allow_agent=False) |
| awgur@10 | 261 else: |
| awgur@10 | 262 self.ssh.connect(hostname=host, username=user, key_filename=key, look_for_keys=False, allow_agent=False) |
| awgur@10 | 263 |
| awgur@10 | 264 if timeout != None: |
| awgur@10 | 265 self.timeout = timeout |
| awgur@10 | 266 else: |
| awgur@10 | 267 self.timeout = TIMEOUT |
| awgur@10 | 268 |
| awgur@10 | 269 self.user = user |
| awgur@10 | 270 self.host = host |
| awgur@10 | 271 |
| awgur@10 | 272 def __del__(self): |
| awgur@10 | 273 try: |
| awgur@10 | 274 self.ssh.close() |
| awgur@10 | 275 except: |
| awgur@10 | 276 pass |
| awgur@10 | 277 del self.ssh |
| awgur@10 | 278 |
| awgur@10 | 279 class InterSSH(_SSH): |
| awgur@10 | 280 """Класс Взаимодействия с сервером SSH |
| awgur@10 | 281 |
| awgur@10 | 282 Кроме аргументов необходимых для метакласса принимает: |
| awgur@10 | 283 - prompt: |
| awgur@10 | 284 Параметр конструктора SSHPromptCheck. Принимается строго по имени. |
| awgur@10 | 285 """ |
| awgur@10 | 286 def __init__(self, *a, **kwa): |
| awgur@10 | 287 if 'prompt' in kwa: |
| awgur@10 | 288 prompt = kwa['prompt'] |
| awgur@10 | 289 del kwa['prompt'] |
| awgur@10 | 290 |
| awgur@10 | 291 _SSH.__init__(self, *a, **kwa) |
| awgur@10 | 292 |
| awgur@10 | 293 self.shell = self.ssh.invoke_shell() |
| awgur@10 | 294 self.shell.settimeout(self.timeout) |
| awgur@10 | 295 if prompt == None: |
| awgur@10 | 296 prompt = self._getPrompt() |
| awgur@10 | 297 self.ready = True |
| awgur@10 | 298 else: |
| awgur@10 | 299 self.ready = False |
| awgur@10 | 300 self.prompt = SSHPromptCheck(prompt) |
| awgur@10 | 301 |
| awgur@10 | 302 |
| awgur@10 | 303 def __bool__(self): |
| awgur@10 | 304 "Готово ли соединение принимать новые комманды" |
| awgur@10 | 305 return self.ready |
| awgur@10 | 306 |
| awgur@10 | 307 def _getPrompt(self): |
| awgur@10 | 308 """Возвращает объект проверки на приглашение комммандной строки из текщего |
| awgur@10 | 309 буфера вывода. |
| awgur@10 | 310 """ |
| awgur@10 | 311 buf = '' |
| awgur@10 | 312 while self.shell.recv_ready(): |
| awgur@10 | 313 buf += self.shell.recv(1024) |
| awgur@10 | 314 |
| awgur@10 | 315 func = promptChecker(self.user, self.host, buf) |
| awgur@10 | 316 return SSHPromptCheck(func) |
| awgur@10 | 317 |
| awgur@10 | 318 |
| awgur@10 | 319 def waitPrompt(self, prompt=None): |
| awgur@10 | 320 """Ожидание приглашения коммандной строки |
| awgur@10 | 321 |
| awgur@10 | 322 Принимает необязательный аргумент: Экземпляр класса SSHPromptCheck, с помощью котороого |
| awgur@10 | 323 проверяется присутствие приглашения командного интерпретатора в выводе. |
| awgur@10 | 324 """ |
| awgur@10 | 325 if prompt == None: |
| awgur@10 | 326 prompt = self.prompt |
| awgur@10 | 327 else: |
| awgur@10 | 328 prompt = SSHPromptCheck(prompt) |
| awgur@10 | 329 |
| awgur@10 | 330 if not self.ready: |
| awgur@10 | 331 timeCheck = TOCheck(self.timeout) |
| awgur@10 | 332 res = SSHOutput() |
| awgur@10 | 333 while True: |
| awgur@10 | 334 while self.shell.recv_stderr_ready(): |
| awgur@10 | 335 res.error(self.shell.recv_stderr(1024)) |
| awgur@10 | 336 |
| awgur@10 | 337 while self.shell.recv_ready(): |
| awgur@10 | 338 res += self.shell.recv(1024) |
| awgur@10 | 339 |
| awgur@10 | 340 if prompt(res): |
| awgur@10 | 341 break |
| awgur@10 | 342 |
| awgur@10 | 343 if timeCheck(): |
| awgur@10 | 344 raise SSHError('Command call timeout\n' + repr(res)) |
| awgur@10 | 345 |
| awgur@10 | 346 self.ready = True |
| awgur@10 | 347 return res |
| awgur@10 | 348 |
| awgur@10 | 349 def __call__(self, cmd, wait=True): |
| awgur@10 | 350 """Запуск команды |
| awgur@10 | 351 |
| awgur@10 | 352 Принимает: |
| awgur@10 | 353 - cmd: Комманда |
| awgur@10 | 354 - wait(необязательный аргумент): Признак ожидания завершения комманды. |
| awgur@10 | 355 |
| awgur@10 | 356 Возвращает: Экземпляр класса SSHOutput |
| awgur@10 | 357 """ |
| awgur@10 | 358 res = SSHOutput() |
| awgur@10 | 359 if not self.ready: |
| awgur@10 | 360 res += self.waitPrompt() |
| awgur@10 | 361 bytes = self.shell.send('%s\n' % cmd) |
| awgur@10 | 362 if bytes == 0: |
| awgur@10 | 363 raise SSHError('Channel closed') |
| awgur@10 | 364 if wait: |
| awgur@10 | 365 res += self.waitPrompt() |
| awgur@10 | 366 else: |
| awgur@10 | 367 self.ready = False |
| awgur@10 | 368 return res |
| awgur@10 | 369 |
| awgur@10 | 370 def subShell(self, cmd, prompt=None): |
| awgur@10 | 371 """Возвращает объект SSHSubsh, через который удобно работать |
| awgur@10 | 372 интерпретаторами, запускаемыми из текущего, либо с ситуацией изменения приглашения |
| awgur@10 | 373 командного интерпретатора. |
| awgur@10 | 374 """ |
| awgur@10 | 375 self(cmd, wait=False) |
| awgur@10 | 376 if prompt == None: |
| awgur@10 | 377 prompt = self._getPropt() |
| awgur@10 | 378 return SSHSubsh(self, prompt) |
| awgur@10 | 379 |
| awgur@10 | 380 def close(self): |
| awgur@10 | 381 res = SSHOutput |
| awgur@10 | 382 if not self: |
| awgur@10 | 383 res += self.waitPrompt() |
| awgur@10 | 384 self.ssh.close() |
| awgur@10 | 385 return res |
| awgur@10 | 386 |
| awgur@10 | 387 class SSH(_SSH): |
| awgur@10 | 388 """Класс призванный управлять выполнением единичных комманд, без привлечения контекста. |
| awgur@10 | 389 |
| awgur@10 | 390 Принимает аргументы необходимые для метакласса _SSH |
| awgur@10 | 391 """ |
| awgur@10 | 392 def __init__(self, *a, **kwa): |
| awgur@10 | 393 _SSH.__init__(self, *a, **kwa) |
| awgur@10 | 394 |
| awgur@10 | 395 def __call__(self, cmd): |
| awgur@10 | 396 return SSHCommunicate(*self.ssh.exec_command(cmd, timeout=self.timeout)) |
| awgur@10 | 397 |