py.lib

Yohn Y. 2019-11-12 Parent:af2bf518950a Child:1668cc57225b

12:7874a3e2281e 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@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