2014-02-26 39 views
2

我的標準程序的PostgreSQL爲遠程訪問服務器上的數據庫是開放的先創建一個SSH隧道爲:Psycopg2訪問的PostgreSQL databese無需手動打開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隧道或「遠程訪問」遠程數據庫,而無需在本地主機上重定向。

是否可以用psycopg2做到這一點?

否則可能打開我的Python代碼中的SSH隧道?

如果我使用:

os.system("ssh [email protected] -L 5432:localhost:5432 -p 222") 

外殼將被重定向到所述遠程主機阻塞主線程的執行。

+0

爲什麼不直接使用遠程PostgreSQL服務器上的SSL,並連接與PostgreSQL的SSL支持? –

回答

-1

對於我使用BSED上this gist解決方案的時刻:

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() 

請讓我知道如果任何人有一個更好的主意

+0

爲什麼被低估? –

+0

腳本中沒有錯誤嗎? ssh_tunnel之前沒有定義過。 – GobSmack

1

通過os.system在單獨的線程/進程中調用您的ssh。您也可以使用ssh使用-N以避免打開遠程shell。

+0

不幸的是,這種解決方案有時候並不依賴於創建隧道的時間 –

+0

這是一個正常的競爭條件。使用'subprocess'模塊而不是'os.system'運行帶'-v'的'ssh',並在輸出中顯示「Local forwarding listening」時等待ssh輸出。使用['threading.Event'](http://docs.python.org/2/library/threading.html#event-objects)來同步調用者和被調用者。 – piro

+0

@piro你能否提供代碼? – nottinhill

-1
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) 
+0

不執行或返回來自「conn = psycopg2.connect()」部分的任何輸入。 – nottinhill

+0

@SirBenBenji在這個答案中沒有什麼可執行的。它所做的唯一的事情就是建立一個ssh連接並按照要求連接到數據庫。你在期待什麼? –

+0

它沒有連接到我的系統上的數據庫,或者我沒有從您的解決方案反饋。光標只是閃爍,我們被「陷入」「-N」。我去了子進程。 os.system可以被認爲是過時的。 – nottinhill

0

Clodo阿爾內託的代碼工作對我來說很好,但要注意它之後不清理流程。

Luca Fiaschi所示的方法也適用於我。我更新了一下python3和更新的psutil模塊。這些更改只是process.username和process.cmdline現在是函數,迭代器是process_iter()而不是get_process_list()。

下面是代碼的一個非常略微修改版本的例子Luca Fiaschi張貼了與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 %[email protected]%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) 
+0

我嘗試執行腳本時出現屬性錯誤。 「SSHTunnel」沒有屬性'ssh_tunnel'。請幫忙。 – GobSmack

+0

它看起來像訪問self.ssh_tunnel的唯一時間是在SSHTunnel.release中。如果該變量不存在,我敢打賭self.ssh_tunnel沒有被設置,這意味着ssh_processes沒有填充任何進程。你如何調用ssh(變量「command」),並在shell執行中自行調用該命令?另外,我將編輯腳本以使其不會出錯。 – mathewguest

2

你也可以使用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(...) 
+0

你可以給一個使用私鑰和密碼的例子嗎?我無法在任何地方看到例子。 –

+0

@ rishabh-sagar,使用'ssh_pkey'和'ssh_private_key_password'關鍵字參數,如https://sshtunnel.readthedocs.org/en/latest/中所述。例如。 'SSHTunnelForwarder(...,ssh_pkey ='/ path/to/keyfile',ssh_private_key_password ='secret',...)' – mrts