py.lib

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

10:af2bf518950a Go to Latest

py.lib/ssh_tools.py

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

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