1
- from urllib . parse import quote
1
+ from tempfile import mkstemp
2
2
import logging
3
+ import os
3
4
4
5
from .base import BaseCommandDBConnector
5
- from .exceptions import DumpError
6
6
7
7
logger = logging .getLogger ('dbbackup.command' )
8
8
9
9
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
10
+ class PgEnvWrapper :
11
+ """
12
+ Context manager that updates the OS environment with the libpq variables
13
+ derived from settings, and if necessary a temporary .pgpass file.
14
+ """
15
+ def __init__ (self , settings ):
16
+ self .settings = settings
17
+ self .pgpass_path = None
18
+
19
+ def __enter__ (self ):
20
+ # Get all settings, with empty defaults to detect later
21
+ pghost = self .settings .get ('HOST' , None )
22
+ pgport = self .settings .get ('PORT' , None )
23
+ pguser = self .settings .get ('USER' , None )
24
+ pgdatabase = self .settings .get ('NAME' , None )
25
+ pgpassword = self .settings .get ('PASSWORD' , None )
26
+
27
+ # Set PG* environment variables for everything we got
28
+ # All defaults are thus left to libpq
29
+ env = os .environ .copy ()
30
+ if pghost :
31
+ env ['PGHOST' ] = pghost
32
+ if pgport :
33
+ env ['PGPORT' ] = pgport
34
+ if pguser :
35
+ env ['PGUSER' ] = pguser
36
+ if pgdatabase :
37
+ env ['PGDATABASE' ] = pgdatabase
38
+
39
+ if pgpassword :
40
+ # Open a temporary file (safe name, mode 600) as .pgpass file
41
+ fd , self .pgpass_path = mkstemp (text = True )
42
+ os .close (fd )
43
+ with open (self .pgpass_path , 'w' ) as pgpass_file :
44
+ # Write a catch-all entry, as this .pgass is only used once and by us
45
+ pgpass_file .write (f'*:*:*:*:{ pgpassword } \n ' )
46
+ env ['PGPASSFILE' ] = self .pgpass_path
47
+
48
+ return env
49
+
50
+ def __exit__ (self , * args ):
51
+ if self .pgpass_path :
52
+ os .unlink (self .pgpass_path )
27
53
28
54
29
55
class PgDumpConnector (BaseCommandDBConnector ):
@@ -39,28 +65,27 @@ class PgDumpConnector(BaseCommandDBConnector):
39
65
40
66
def _create_dump (self ):
41
67
cmd = '{} ' .format (self .dump_cmd )
42
- cmd = cmd + create_postgres_uri (self )
43
68
44
69
for table in self .exclude :
45
70
cmd += ' --exclude-table-data={}' .format (table )
46
71
if self .drop :
47
72
cmd += ' --clean'
48
73
49
74
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 })
51
77
return stdout
52
78
53
79
def _restore_dump (self , dump ):
54
80
cmd = '{} ' .format (self .restore_cmd )
55
- cmd = cmd + create_postgres_uri (self )
56
81
57
82
# without this, psql terminates with an exit value of 0 regardless of errors
58
83
cmd += ' --set ON_ERROR_STOP=on'
59
84
if self .single_transaction :
60
85
cmd += ' --single-transaction'
61
- cmd += ' {}' .format (self .settings ['NAME' ])
62
86
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 })
64
89
return stdout , stderr
65
90
66
91
@@ -76,11 +101,8 @@ def _enable_postgis(self):
76
101
self .psql_cmd )
77
102
cmd += ' --username={}' .format (self .settings ['ADMIN_USER' ])
78
103
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 )
84
106
85
107
def _restore_dump (self , dump ):
86
108
if self .settings .get ('ADMIN_USER' ):
@@ -101,23 +123,24 @@ class PgDumpBinaryConnector(PgDumpConnector):
101
123
102
124
def _create_dump (self ):
103
125
cmd = '{} ' .format (self .dump_cmd )
104
- cmd = cmd + create_postgres_uri (self )
105
126
106
127
cmd += ' --format=custom'
107
128
for table in self .exclude :
108
129
cmd += ' --exclude-table-data={}' .format (table )
109
130
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 })
111
133
return stdout
112
134
113
135
def _restore_dump (self , dump ):
114
- dbname = create_postgres_uri (self )
115
- cmd = '{} {}' .format (self .restore_cmd , dbname )
136
+ cmd = '{} ' .format (self .restore_cmd )
116
137
117
138
if self .single_transaction :
118
139
cmd += ' --single-transaction'
119
140
if self .drop :
120
141
cmd += ' --clean'
142
+ cmd += '-d {}' .format (self .settings .get ('NAME' ))
121
143
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 })
123
146
return stdout , stderr
0 commit comments