15 |
15 |
# along with this program; if not, see <http://www.gnu.org/licenses/>.
|
16 |
16 |
|
17 |
17 |
import tarfile
|
18 |
|
import time
|
19 |
18 |
import os
|
|
19 |
import shutil
|
|
20 |
import zipfile
|
|
21 |
import subprocess
|
|
22 |
import tempfile
|
|
23 |
from datetime import datetime
|
20 |
24 |
|
21 |
25 |
from qommon.ctl import Command, make_option
|
22 |
26 |
|
23 |
27 |
|
|
28 |
class CmdError(Exception):
|
|
29 |
pass
|
|
30 |
|
|
31 |
|
24 |
32 |
class CmdBackup(Command):
|
25 |
33 |
name = 'backup'
|
26 |
34 |
|
27 |
35 |
def __init__(self):
|
28 |
36 |
Command.__init__(self, [
|
29 |
|
make_option('--file', metavar='FILENAME', action='store',
|
30 |
|
dest='filename', default=None)
|
|
37 |
make_option('--destination', action='store',
|
|
38 |
dest='destination', default=None)
|
31 |
39 |
])
|
32 |
40 |
|
33 |
41 |
def execute(self, base_options, sub_options, args):
|
... | ... | |
42 |
50 |
if not os.path.exists(pub.app_dir):
|
43 |
51 |
return 1
|
44 |
52 |
|
45 |
|
if sub_options.filename:
|
46 |
|
backup_filepath = sub_options.filename
|
|
53 |
pub.set_config()
|
|
54 |
self.backup_instance(pub, sub_options, args)
|
|
55 |
|
|
56 |
def backup_instance(self, pub, sub_options, args):
|
|
57 |
instance_name = args[0].replace('.', '_').replace('-', '_')
|
|
58 |
file_name = 'wcs__%s__%s' % (instance_name,
|
|
59 |
datetime.now().date().isoformat())
|
|
60 |
dest_dir = sub_options.destination
|
|
61 |
if dest_dir:
|
|
62 |
if not os.path.exists(dest_dir):
|
|
63 |
raise CmdError('destination directory does not exist')
|
47 |
64 |
else:
|
48 |
|
backup_dir = os.path.join(pub.app_dir, 'backups')
|
49 |
|
if not os.path.exists(backup_dir):
|
50 |
|
os.mkdir(backup_dir)
|
51 |
|
backup_filepath = os.path.join(backup_dir,
|
52 |
|
'backup-%s%s%s-%s%s%s.tar.gz' % time.localtime()[:6])
|
53 |
|
|
54 |
|
backup = tarfile.open(backup_filepath, mode='w:gz')
|
55 |
|
for basename, dirnames, filenames in os.walk(pub.app_dir):
|
56 |
|
if 'backups' in dirnames: # do not recurse in backup directory
|
57 |
|
idx = dirnames.index('backups')
|
58 |
|
dirnames[idx:idx+1] = []
|
59 |
|
for filename in filenames:
|
60 |
|
backup.add(os.path.join(basename, filename),
|
61 |
|
os.path.join(basename, filename)[len(pub.app_dir):])
|
|
65 |
dest_dir = os.path.join(pub.app_dir, 'backups')
|
|
66 |
if not os.path.exists(dest_dir):
|
|
67 |
os.mkdir(dest_dir)
|
|
68 |
try:
|
|
69 |
temp_dir = tempfile.mkdtemp()
|
|
70 |
except IOError as e:
|
|
71 |
raise CmdError('an error occurred while creating temporary directory: %s' % e.message)
|
|
72 |
|
|
73 |
try:
|
|
74 |
instance_tar_path = self.backup_instance_dir(pub, file_name, temp_dir)
|
|
75 |
if pub.is_using_postgresql():
|
|
76 |
dump_path = self.backup_postgresql(pub, file_name, temp_dir)
|
|
77 |
self.create_backup_zip(dump_path, instance_tar_path, file_name, dest_dir)
|
|
78 |
else:
|
|
79 |
try:
|
|
80 |
# move only the tar file to the destination directory as there's no need
|
|
81 |
# to make a zip
|
|
82 |
shutil.move(instance_tar_path, dest_dir)
|
|
83 |
except IOError as e:
|
|
84 |
raise CmdError('could not move instance tar %s to %s: %s' % (instance_tar_path,
|
|
85 |
dest_dir,
|
|
86 |
e.message))
|
|
87 |
finally:
|
|
88 |
try:
|
|
89 |
shutil.rmtree(temp_dir)
|
|
90 |
except IOError as e:
|
|
91 |
print 'could not remove temporary directory %s: %s' % (temp_dir, e.message)
|
|
92 |
|
|
93 |
|
|
94 |
def backup_instance_dir(self, pub, file_name, temp_dir):
|
|
95 |
backup_filepath = os.path.join(temp_dir, '%s.tar.gz' % file_name)
|
|
96 |
try:
|
|
97 |
backup = tarfile.open(backup_filepath, mode='w:gz')
|
|
98 |
|
|
99 |
for basename, dirnames, filenames in os.walk(pub.app_dir):
|
|
100 |
if 'backups' in dirnames: # do not recurse in backup directory
|
|
101 |
idx = dirnames.index('backups')
|
|
102 |
dirnames[idx:idx+1] = []
|
|
103 |
for filename in filenames:
|
|
104 |
backup.add(os.path.join(basename, filename),
|
|
105 |
os.path.join(basename, filename)[len(pub.app_dir):])
|
|
106 |
except IOError as e:
|
|
107 |
raise CmdError('could not open tar file %s: %s' % (backup_filepath,
|
|
108 |
e.message))
|
|
109 |
except tarfile.TarError:
|
|
110 |
raise CmdError('an error occured while making tar archive for %s' % file_name)
|
62 |
111 |
|
63 |
112 |
backup.close()
|
64 |
113 |
|
|
114 |
return backup_filepath
|
|
115 |
|
|
116 |
def backup_postgresql(self, pub, file_name, temp_dir):
|
|
117 |
db_conf = pub.cfg['postgresql']
|
|
118 |
# use 'or' as value are at None when not set
|
|
119 |
db_name = db_conf.get('database') or ''
|
|
120 |
db_host = db_conf.get('host') or ''
|
|
121 |
db_user = db_conf.get('user') or ''
|
|
122 |
db_port = db_conf.get('port') or ''
|
|
123 |
db_pwd = db_conf.get('password') or ''
|
|
124 |
if db_pwd:
|
|
125 |
os.environ['PGPASSWORD'] = db_pwd
|
|
126 |
|
|
127 |
dump_path = os.path.join(temp_dir, '%s.sql' % file_name)
|
|
128 |
|
|
129 |
try:
|
|
130 |
subprocess.check_call(['pg_dump', '-Oc', '-h', db_host,
|
|
131 |
'-U', db_user, '-p', db_port,
|
|
132 |
'-w', '-f', dump_path, '-d', db_name])
|
|
133 |
except subprocess.CalledProcessError as e:
|
|
134 |
raise CmdError('an error occured while dumping instance database %s: %s' % (db_name,
|
|
135 |
e.output))
|
|
136 |
if db_pwd:
|
|
137 |
os.environ.pop('PGPASSWORD')
|
|
138 |
|
|
139 |
return dump_path
|
|
140 |
|
|
141 |
def create_backup_zip(self, dump_path, instance_tar_path, file_name, dest_dir):
|
|
142 |
zip_path = os.path.join(dest_dir, '%s.zip' % file_name)
|
|
143 |
try:
|
|
144 |
with zipfile.ZipFile(zip_path, 'a') as zip_file:
|
|
145 |
zip_file.write(instance_tar_path,
|
|
146 |
os.path.basename(instance_tar_path))
|
|
147 |
zip_file.write(dump_path,
|
|
148 |
os.path.basename(dump_path))
|
|
149 |
except RuntimeError as e:
|
|
150 |
if os.path.exists(zip_path):
|
|
151 |
try:
|
|
152 |
os.remove(zip_path)
|
|
153 |
except OSError as e:
|
|
154 |
print 'could not remove zip file %s: %s' % (zip_path, e.message)
|
|
155 |
raise CmdError('an error occured while making zip %s: %s' % (zip_path,
|
|
156 |
e.message))
|
|
157 |
|
65 |
158 |
|
66 |
159 |
CmdBackup.register()
|
67 |
|
-
|