-
Notifications
You must be signed in to change notification settings - Fork 16
/
pgsql.py
158 lines (122 loc) · 4.15 KB
/
pgsql.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
#
# Copyright (c) 2013 Liraz Siri <[email protected]>
#
# This file is part of TKLBAM (TurnKey GNU/Linux BAckup and Migration).
#
# TKLBAM is open source software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License as
# published by the Free Software Foundation; either version 3 of
# the License, or (at your option) any later version.
#
import sys
import os
from os.path import *
import re
import commands
import shutil
from executil import system, getoutput, getoutput_popen
from dblimits import DBLimits
FNAME_GLOBALS = ".globals.sql"
FNAME_MANIFEST = "manifest.txt"
class Error(Exception):
pass
def su(command):
return "su postgres -c" + commands.mkarg(command)
def list_databases():
for line in getoutput(su('psql -l')).splitlines():
m = re.match(r'^ (\S+?)\s', line)
if not m:
continue
name = m.group(1)
yield name
def dumpdb(outdir, name, tlimits=[]):
path = join(outdir, name)
if isdir(path):
shutil.rmtree(path)
os.makedirs(path)
# format pg_dump command
pg_dump = "pg_dump --format=tar"
for (table, sign) in tlimits:
if sign:
pg_dump += " --table=" + table
else:
pg_dump += " --exclude-table=" + table
pg_dump += " " + name
manifest = getoutput(su(pg_dump) + " | tar xvC %s" % path)
file(join(path, FNAME_MANIFEST), "w").write(manifest + "\n")
def restoredb(dbdump, dbname, tlimits=[]):
manifest = file(join(dbdump, FNAME_MANIFEST)).read().splitlines()
# remove any malformed entries
manifest = [i for i in manifest if not i.endswith('Permission denied')]
try:
getoutput(su("dropdb " + dbname))
except:
pass
orig_cwd = os.getcwd()
os.chdir(dbdump)
try:
command = "tar c %s 2>/dev/null" % " ".join(manifest)
command += " | pg_restore --create --dbname=postgres --format=tar"
for (table, sign) in tlimits:
if sign:
command += " --table=" + table
command += " | " + su("cd $HOME; psql")
system(command)
finally:
os.chdir(orig_cwd)
def pgsql2fs(outdir, limits=[], callback=None):
limits = DBLimits(limits)
for dbname in list_databases():
if dbname not in limits or dbname == 'postgres' or re.match(r'template\d', dbname):
continue
if callback:
callback(dbname)
dumpdb(outdir, dbname, limits[dbname])
globals = getoutput(su("pg_dumpall --globals"))
file(join(outdir, FNAME_GLOBALS), "w").write(globals)
def fs2pgsql(outdir, limits=[], callback=None):
limits = DBLimits(limits)
for (database, table) in limits.tables:
if (database, table) not in limits:
raise Error("can't exclude %s/%s: table excludes not supported for postgres" % (database, table))
# load globals first, suppress noise (e.g., "ERROR: role "postgres" already exists)
globals = file(join(outdir, FNAME_GLOBALS)).read()
getoutput_popen(su("psql -q -o /dev/null"), globals)
for dbname in os.listdir(outdir):
fpath = join(outdir, dbname)
if not isdir(fpath) or dbname not in limits:
continue
if callback:
callback(dbname)
restoredb(fpath, dbname, limits[dbname])
def cb_print(fh=None):
if not fh:
fh = sys.stdout
def func(val):
print >> fh, "database: " + val
return func
def backup(outdir, limits=[], callback=None):
if isdir(outdir):
shutil.rmtree(outdir)
if not exists(outdir):
os.makedirs(outdir)
try:
pgsql2fs(outdir, limits, callback)
except Exception, e:
if isdir(outdir):
shutil.rmtree(outdir)
raise Error("pgsql backup failed: " + str(e))
def restore(path, limits=[], callback=None):
try:
fs2pgsql(path, limits, callback=callback)
except Exception, e:
raise Error("pgsql restore failed: " + str(e))
class PgsqlService:
INIT_SCRIPT = "/etc/init.d/postgresql"
@classmethod
def is_running(cls):
try:
getoutput(cls.INIT_SCRIPT, "status")
return True
except:
return False