1- from urllib . parse import quote
1+ from tempfile import mkstemp
22import logging
3+ import os
34
45from .base import BaseCommandDBConnector
56from .exceptions import DumpError
67
78logger = logging .getLogger ('dbbackup.command' )
89
910
10- def create_postgres_uri (self ):
11- host = self .settings .get ('HOST' )
12- if not host :
13- raise DumpError ('A host name is required' )
14-
15- dbname = self .settings .get ('NAME' ) or ''
16- user = quote (self .settings .get ('USER' ) or '' )
17- password = self .settings .get ('PASSWORD' ) or ''
18- password = ':{}' .format (quote (password )) if password else ''
19- if not user :
20- password = ''
21- else :
22- host = '@' + host
23-
24- port = ':{}' .format (self .settings .get ('PORT' )) if self .settings .get ('PORT' ) else ''
25- dbname = f'--dbname=postgresql://{ user } { password } { host } { port } /{ dbname } '
26- return dbname
11+ class PgEnvWrapper :
12+ """
13+ Context manager that updates the OS environment with the libpq variables
14+ derived from settings, and if necessary a temporary .pgpass file.
15+ """
16+ def __init__ (self , settings ):
17+ self .settings = settings
18+ self .pgpass_fd , self .pgpass_path = None , None
19+
20+ def __enter__ (self ):
21+ # Get all settings, with empty defaults to detect later
22+ pghost = self .settings .get ('HOST' , None )
23+ pgport = self .settings .get ('PORT' , None )
24+ pguser = self .settings .get ('USER' , None )
25+ pgdatabase = self .settings .get ('NAME' , None )
26+ pgpassword = self .settings .get ('PASSWORD' , None )
27+
28+ # Set PG* environment variables for everything we got
29+ # All defaults are thus left to libpq
30+ env = os .environ .copy ()
31+ if pghost :
32+ env ['PGHOST' ] = pghost
33+ if pgport :
34+ env ['PGPORT' ] = pgport
35+ if pguser :
36+ env ['PGUSER' ] = pguser
37+ if pgdatabase :
38+ env ['PGDATABASE' ] = pgdatabase
39+
40+ if pgpassword :
41+ # Open a temporary file (safe name, mode 600) as .pgpass file
42+ fd , self .pgpass_path = mkstemp (text = True )
43+ os .close (fd )
44+ with open (self .pgpass_path , 'w' ) as pgpass_file :
45+ # Write a catch-all entry, as this .pgass is only used once and by us
46+ pgpass_file .write (f'*:*:*:*:{ pgpassword } \n ' )
47+ env ['PGPASSFILE' ] = self .pgpass_path
48+
49+ return env
50+
51+ def __exit__ (self , * args ):
52+ os .unlink (self .pgpass_path )
2753
2854
2955class PgDumpConnector (BaseCommandDBConnector ):
@@ -39,28 +65,27 @@ class PgDumpConnector(BaseCommandDBConnector):
3965
4066 def _create_dump (self ):
4167 cmd = '{} ' .format (self .dump_cmd )
42- cmd = cmd + create_postgres_uri (self )
4368
4469 for table in self .exclude :
4570 cmd += ' --exclude-table-data={}' .format (table )
4671 if self .drop :
4772 cmd += ' --clean'
4873
4974 cmd = '{} {} {}' .format (self .dump_prefix , cmd , self .dump_suffix )
50- stdout , stderr = self .run_command (cmd , env = self .dump_env )
75+ with PgEnvWrapper (self .settings ) as env :
76+ stdout , stderr = self .run_command (cmd , env = {** self .dump_env , ** env })
5177 return stdout
5278
5379 def _restore_dump (self , dump ):
5480 cmd = '{} ' .format (self .restore_cmd )
55- cmd = cmd + create_postgres_uri (self )
5681
5782 # without this, psql terminates with an exit value of 0 regardless of errors
5883 cmd += ' --set ON_ERROR_STOP=on'
5984 if self .single_transaction :
6085 cmd += ' --single-transaction'
61- cmd += ' {}' .format (self .settings ['NAME' ])
6286 cmd = '{} {} {}' .format (self .restore_prefix , cmd , self .restore_suffix )
63- stdout , stderr = self .run_command (cmd , stdin = dump , env = self .restore_env )
87+ with PgEnvWrapper (self .settings ) as env :
88+ stdout , stderr = self .run_command (cmd , stdin = dump , env = {** self .restore_env , ** env })
6489 return stdout , stderr
6590
6691
@@ -76,11 +101,8 @@ def _enable_postgis(self):
76101 self .psql_cmd )
77102 cmd += ' --username={}' .format (self .settings ['ADMIN_USER' ])
78103 cmd += ' --no-password'
79- if self .settings .get ('HOST' ):
80- cmd += ' --host={}' .format (self .settings ['HOST' ])
81- if self .settings .get ('PORT' ):
82- cmd += ' --port={}' .format (self .settings ['PORT' ])
83- return self .run_command (cmd )
104+ with PgEnvWrapper (self .settings ) as env :
105+ return self .run_command (cmd , env = env )
84106
85107 def _restore_dump (self , dump ):
86108 if self .settings .get ('ADMIN_USER' ):
@@ -101,23 +123,24 @@ class PgDumpBinaryConnector(PgDumpConnector):
101123
102124 def _create_dump (self ):
103125 cmd = '{} ' .format (self .dump_cmd )
104- cmd = cmd + create_postgres_uri (self )
105126
106127 cmd += ' --format=custom'
107128 for table in self .exclude :
108129 cmd += ' --exclude-table-data={}' .format (table )
109130 cmd = '{} {} {}' .format (self .dump_prefix , cmd , self .dump_suffix )
110- stdout , stderr = self .run_command (cmd , env = self .dump_env )
131+ with PgEnvWrapper (self .settings ) as env :
132+ stdout , stderr = self .run_command (cmd , env = {** self .dump_env , ** env })
111133 return stdout
112134
113135 def _restore_dump (self , dump ):
114- dbname = create_postgres_uri (self )
115- cmd = '{} {}' .format (self .restore_cmd , dbname )
136+ cmd = '{} ' .format (self .restore_cmd )
116137
117138 if self .single_transaction :
118139 cmd += ' --single-transaction'
119140 if self .drop :
120141 cmd += ' --clean'
142+ cmd += '-d {}' .format (self .settings .get ('NAME' ))
121143 cmd = '{} {} {}' .format (self .restore_prefix , cmd , self .restore_suffix )
122- stdout , stderr = self .run_command (cmd , stdin = dump , env = self .restore_env )
144+ with PgEnvWrapper (self .settings ) as env :
145+ stdout , stderr = self .run_command (cmd , stdin = dump , env = {** self .restore_env , ** env })
123146 return stdout , stderr
0 commit comments