py.lib

Yohn Y. 2022-07-17 Parent:1668cc57225b

27:54d7d7b9350b Go to Latest

py.lib/ssh_tools.py

. Исправление в форматировании

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