Psycopg2 доступ к базе данных PostgreSQL на удаленном хосте без ручного открытия ssh туннеля



Моя стандартная процедура PostgreSQL для доступа к базе данных на удаленном сервере открыта сначала создайте ssh туннель как:

ssh username1@remote.somewhere.com -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 username1@remote.somewhere.com -L 5432:localhost:5432 -p 222")

Оболочка будет перенаправлена на удаленный хост, блокирующий выполнение основного потока.

295   5  

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 username@someserver.com -L %d:localhost:%d -p 222 -fN" % (someport, someport)

with SSHTunnel(command):
    test()

Пожалуйста, дайте мне знать, если у кого-нибудь есть идея получше

from time import sleep

os.system("ssh username1@remote.somewhere.com -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)
    Ничего не найдено.

Добавить ответ:
Отменить.