-
Notifications
You must be signed in to change notification settings - Fork 0
/
couchdb-backup.py
executable file
·111 lines (90 loc) · 4.09 KB
/
couchdb-backup.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
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import argparse
import json
import os
import hashlib
import shutil
import concurrent.futures
import re
### Functions
def process_database(database):
if args.match and not re_match.match(database):
return "No match for DB " + database + ""
if args.exclude and re_exclude.match(database):
return "Excluding match for DB " + database + ""
log_buffer = []
database_hashed = hashlib.sha1(database.encode('utf-8')).hexdigest()
log_buffer.append('Dumping: {} - {}'.format(database_hashed, database))
filehandle = open(path_dump + "/{}.json".format(database_hashed), "a")
filehandle.truncate(0)
filehandle.write(database + "\n")
buffer = ""
for document in client[database]:
buffer += json.dumps(document) + "\n"
if "_attachments" in document:
for attachment in document["_attachments"]:
attachment_hashed = hashlib.sha1(document["_attachments"][attachment]['digest'].encode('utf-8')).hexdigest()
log_buffer.append('└ attachment: {} - {}'.format(attachment, attachment_hashed))
file_attachment = open(path_attachments + attachment_hashed, "wb")
document.get_attachment(attachment, write_to=file_attachment, attachment_type='binary')
file_attachment.close()
filehandle.write(buffer)
filehandle.close()
client[database].clear()
return "\n".join(log_buffer)
### Functions
if __name__ == "__main__":
parser = argparse.ArgumentParser(description='Dumps CouchDB / Bigcouch databases')
parser.add_argument('--host', help='FQDN or IP, including port. Default: http://localhost:5984', default='http://localhost:5984')
parser.add_argument('--user', help='DB username. Default: none')
parser.add_argument('--password', help='DB password. Default: none')
parser.add_argument('--match', help='Regular expression to match the DB names. Example ".*-myprogram|users|.*bkp.*". Default: None.')
parser.add_argument('--exclude', help='Regular expression to match the DB names for exclusion. Example ".*-myprogram|users|.*bkp.*". Default: None.')
args = parser.parse_args()
print(args)
path = os.getcwd()
path_dump = path + "/dumps/"
path_attachments = path + "/dumps/attachments/"
print ("DB dump to be stored at %s" % path_dump)
if os.path.isdir(path_dump):
shutil.rmtree(path_dump)
if not os.path.isdir(path_attachments):
try:
os.makedirs(path_attachments)
except OSError:
print ("Creation of the directory %s failed" % path_attachments)
else:
print ("Successfully created the directory %s " % path_attachments)
from cloudant.client import Cloudant
client = Cloudant(args.user,
args.password,
url=args.host,
admin_party=not (args.user and args.password),
use_basic_auth=(args.user and args.password),
connect=True,
auto_renew=True
)
session = client.session()
if session:
print('Username: {0}'.format(session.get('userCtx', {}).get('name')))
if args.match:
re_match = re.compile(args.match)
print ('Regular expresion will be used to filter databases')
if args.exclude:
re_exclude = re.compile(args.exclude)
print ('Regular expresion will be used to filter databases for exclusion')
print ("===")
with concurrent.futures.ThreadPoolExecutor(max_workers=8) as executor:
future_import = {executor.submit(process_database, database): database for database in client.all_dbs()}
for future in concurrent.futures.as_completed(future_import):
file = future_import[future]
try:
data = future.result()
except Exception as exc:
print('%r generated an exception: %s' % (file, exc))
else:
print(data)
print ("Compressing DUMP folder")
shutil.make_archive("dump", 'zip', path_dump)
print ("all done!")