Psycopg2 доступ к базе данных PostgreSQL на удаленном хосте без ручного открытия ssh туннеля
Моя стандартная процедура PostgreSQL для доступа к базе данных на удаленном сервере открыта сначала создайте ssh туннель как:
ssh [email protected] -L 5432:localhost:5432 -p 222
А затем выполнить мой запрос на python из другой оболочки следующим образом:
conn = psycopg2.connect("host=localhost" + " dbname=" +
conf.dbname + " user=" + conf.user +
" password=" + conf.password)
cur = conn.cursor()
cur.execute(query)
Этот фрагмент кода python прекрасно работает после создания туннеля. Тем не менее, я хотел бы, чтобы psycopg2 уже открыл SSH-туннель или достиг "каким-то образом" удаленной базы данных без необходимости перенаправлять ее на мой localhost.
Возможно ли это сделать с psycopg2?
Является иначе можно открыть ssh туннель в моем коде python?
Если я использую:
os.system("ssh [email protected] -L 5432:localhost:5432 -p 222")
Оболочка будет перенаправлена на удаленный хост, блокирующий выполнение основного потока.
5 ответов:
Вы также можете использовать sshtunnel , короткий и сладкий:
from sshtunnel.sshtunnel import SSHTunnelForwarder PORT=5432 with SSHTunnelForwarder((REMOTE_HOST, REMOTE_SSH_PORT), ssh_username=REMOTE_USERNAME, ssh_password=REMOTE_PASSWORD, remote_bind_address=('localhost', PORT), local_bind_address=('localhost', PORT)): conn = psycopg2.connect(...)
Вызовите ssh через
os.systemв отдельном потоке/процессе. Вы также можете использовать-Nс ssh, чтобы избежать открытия удаленной оболочки.
Код Клодоальдо Нето работал для меня идеально, но будьте осторожны, он не очищает процесс после этого.
Метод, показанный Лукой Фиаски, также работает для меня. Я немного обновил его для python3 и обновленного модуля psutil. Изменения были именно таким процессом.имя пользователя и процесс.cmdline теперь являются функциями и что итератором является process_iter () вместо get_process_list ().Вот пример очень слегка измененной версии кода, опубликованной Лукой Фиаски, которая работает с python3 (требуется модуль psutil). Я надеюсь, что это, по крайней мере, в основном правильно!
#!/usr/bin/env python3 import psutil import psycopg2 import subprocess import time import os # Tunnel Config SSH_HOST = "111.222.333.444" SSH_USER = "user" SSH_KEYFILE = "key.pem" SSH_FOREIGN_PORT = 5432 # Port that postgres is running on the foreign server SSH_INTERNAL_PORT = 5432 # Port we open locally that is forwarded to # FOREIGN_PORT on the server. # Postgres Config DB_HOST = "127.0.0.1" DB_PORT = SSH_INTERNAL_PORT DB_PASSWORD = "password" DB_DATABASE = "postgres" DB_USER = "user" class SSHTunnel(object): """ A context manager implementation of an ssh tunnel opened from python """ def __init__(self, tunnel_command): assert "-fN" in tunnel_command, "need to open the tunnel with -fN" self._tunnel_command = tunnel_command self._delay = 0.1 self.ssh_tunnel = None def create_tunnel(self): tunnel_cmd = self._tunnel_command ssh_process = subprocess.Popen(tunnel_cmd, universal_newlines=True, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, stdin=subprocess.PIPE) # Assuming that the tunnel command has "-f" and "ExitOnForwardFailure=yes", then the # command will return immediately so we can check the return status with a poll(). while True: p = ssh_process.poll() if p is not None: break time.sleep(self._delay) if p == 0: # Unfortunately there is no direct way to get the pid of the spawned ssh process, so we'll find it # by finding a matching process using psutil. current_username = psutil.Process(os.getpid()).username() ssh_processes = [proc for proc in psutil.process_iter() if proc.cmdline() == tunnel_cmd.split() and proc.username() == current_username] if len(ssh_processes) == 1: self.ssh_tunnel = ssh_processes[0] return ssh_processes[0] else: raise RuntimeError('multiple (or zero?) tunnel ssh processes found: ' + str(ssh_processes)) else: raise RuntimeError('Error creating tunnel: ' + str(p) + ' :: ' + str(ssh_process.stdout.readlines())) def release(self): """ Get rid of the tunnel by killin the pid """ if self.ssh_tunnel: self.ssh_tunnel.terminate() def __enter__(self): self.create_tunnel() return self def __exit__(self, type, value, traceback): self.release() def __del__(self): self.release() command = "ssh -i %s %s@%s -fNL %d:localhost:%d"\ % (SSH_KEYFILE, SSH_USER, SSH_HOST, SSH_INTERNAL_PORT, SSH_FOREIGN_PORT) with SSHTunnel(command): conn = psycopg2.connect(host = DB_HOST, password = DB_PASSWORD, database = DB_DATABASE, user = DB_USER, port = DB_PORT) curs = conn.cursor() sql = "select * from table" curs.execute(sql) rows = curs.fetchall() print(rows)
На данный момент я использую решение, основанное на этой сути :
class SSHTunnel(object): """ A context manager implementation of an ssh tunnel opened from python """ def __init__(self, tunnel_command): assert "-fN" in tunnel_command, "need to open the tunnel with -fN" self._tunnel_command = tunnel_command self._delay = 0.1 def create_tunnel(self): tunnel_cmd = self._tunnel_command import time, psutil, subprocess ssh_process = subprocess.Popen(tunnel_cmd, universal_newlines=True, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, stdin=subprocess.PIPE) # Assuming that the tunnel command has "-f" and "ExitOnForwardFailure=yes", then the # command will return immediately so we can check the return status with a poll(). while True: p = ssh_process.poll() if p is not None: break time.sleep(self._delay) if p == 0: # Unfortunately there is no direct way to get the pid of the spawned ssh process, so we'll find it # by finding a matching process using psutil. current_username = psutil.Process(os.getpid()).username ssh_processes = [proc for proc in psutil.get_process_list() if proc.cmdline == tunnel_cmd.split() and proc.username == current_username] if len(ssh_processes) == 1: self.ssh_tunnel = ssh_processes[0] return ssh_processes[0] else: raise RuntimeError, 'multiple (or zero?) tunnel ssh processes found: ' + str(ssh_processes) else: raise RuntimeError, 'Error creating tunnel: ' + str(p) + ' :: ' + str(ssh_process.stdout.readlines()) def release(self): """ Get rid of the tunnel by killin the pid """ self.ssh_tunnel.terminate() def __enter__(self): self.create_tunnel() return self def __exit__(self, type, value, traceback): self.release() def __del__(self): self.release() def test(): #do things that will fail if the tunnel is not opened print "done ==========" command = "ssh [email protected] -L %d:localhost:%d -p 222 -fN" % (someport, someport) with SSHTunnel(command): test()Пожалуйста, дайте мне знать, если у кого-нибудь есть идея получше
from time import sleep os.system("ssh [email protected] -fNL 5432:localhost:5432 -p 222") while True: try: conn = psycopg2.connect( "host=localhost dbname={0} user={1} password={2}".format( conf.dbname, conf.user, conf.password ) ) break except psycopg2.OperationalError: sleep(3)
Comments