rewriting backup script; config and plugins done, just need to parse it and execute

This commit is contained in:
brent s
2019-05-23 02:46:05 -04:00
parent 36061cccb5
commit 130746fa00
7 changed files with 1203 additions and 226 deletions

View File

@@ -0,0 +1,96 @@
import os
# TODO: virtual env?
import ldap
import ldif
# Designed for use with OpenLDAP in an OLC configuration.
class Backup(object):
def __init__(self,
server = 'ldap://sub.domain.tld',
port = 389,
basedn = 'dc=domain,dc=tld',
sasl = False,
starttls = True,
binddn = 'cn=Manager,dc=domain,dc=tld',
password_file = '~/.ldap.pass',
password = None,
outdir = '~/.cache/backup/ldap',
splitldifs = True):
self.server = server
self.port = port
self.basedn = basedn
self.sasl = sasl
self.binddn = binddn
self.outdir = os.path.abspath(os.path.expanduser(outdir))
os.makedirs(self.outdir, exist_ok = True)
self.splitldifs = splitldifs
self.starttls = starttls
if password_file and not password:
with open(os.path.abspath(os.path.expanduser(password_file)), 'r') as f:
self.password = f.read().strip()
else:
self.password = password
# Human readability, yay.
# A note, SSLv3 is 0x300. But StartTLS can only be done with TLS, not SSL, I *think*?
# PRESUMABLY, now that it's finalized, TLS 1.3 will be 0x304.
# See https://tools.ietf.org/html/rfc5246#appendix-E
self._tlsmap = {'1.0': int(0x301), # 769
'1.1': int(0x302), # 770
'1.2': int(0x303)} # 771
self._minimum_tls_ver = '1.2'
if self.sasl:
self.server = 'ldapi:///'
self.cxn = None
self.connect()
self.dump()
self.close()
def connect(self):
self.cxn = ldap.initialize(self.server)
self.cxn.set_option(ldap.OPT_REFERRALS, 0)
self.cxn.set_option(ldap.OPT_PROTOCOL_VERSION, 3)
if not self.sasl:
if self.starttls:
self.cxn.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_NEVER)
self.cxn.set_option(ldap.OPT_X_TLS, ldap.OPT_X_TLS_DEMAND)
self.cxn.set_option(ldap.OPT_X_TLS_DEMAND, True)
self.cxn.set_option(ldap.OPT_X_TLS_PROTOCOL_MIN, self._tlsmap[self._minimum_tls_ver])
if self.sasl:
self.cxn.sasl_external_bind_s()
else:
if self.starttls:
self.cxn.start_tls_s()
self.cxn.bind_s(self.binddn, self.password)
return()
def dump(self):
dumps = {'schema': 'cn=config',
'data': self.basedn}
with open(os.path.join(self.outdir, ('ldap-config.ldif' if self.splitldifs else 'ldap.ldif')), 'w') as f:
l = ldif.LDIFWriter(f)
rslts = self.cxn.search_s(dumps['schema'],
ldap.SCOPE_SUBTREE,
filterstr = '(objectClass=*)',
attrlist = ['*', '+'])
for r in rslts:
l.unparse(r[0], r[1])
if self.splitldifs:
f = open(os.path.join(self.outdir, 'ldap-data.ldif'), 'w')
else:
f = open(os.path.join(self.outdir, 'ldap.ldif'), 'a')
rslts = self.cxn.search_s(dumps['data'],
ldap.SCOPE_SUBTREE,
filterstr = '(objectClass=*)',
attrlist = ['*', '+'])
l = ldif.LDIFWriter(f)
for r in rslts:
l.unparse(r[0], r[1])
f.close()
def close(self):
if self.cxn:
self.cxn.unbind_s()
return()

View File

@@ -0,0 +1,72 @@
import copy
import os
import re
import subprocess
import warnings
_mysql_ssl_re = re.compile('^ssl-(.*)$')
# TODO: is it possible to do a pure-python dump via PyMySQL?
class Backup(object):
def __init__(self, dbs = None,
cfg = '~/.my.cnf',
cfgsuffix = '',
splitdumps = True,
dumpopts = None,
mysqlbin = 'mysql',
mysqldumpbin = 'mysqldump',
outdir = '~/.cache/backup/mysql'):
# If dbs is None, we dump ALL databases.
self.dbs = dbs
self.cfgsuffix = cfgsuffix
self.splitdumps = splitdumps
self.mysqlbin = mysqlbin
self.mysqldumpbin = mysqldumpbin
self.outdir = os.path.abspath(os.path.expanduser(outdir))
self.cfg = os.path.abspath(os.path.expanduser(cfg))
os.makedirs(self.outdir, exist_ok = True)
if not os.path.isfile(self.cfg):
raise OSError(('{0} does not exist!').format(self.cfg))
if not dumpopts:
self.dumpopts = ['--routines',
'--add-drop-database',
'--add-drop-table',
'--allow-keywords',
'--complete-insert',
'--create-options',
'--extended-insert']
else:
self.dumpopts = dumpopts
self.getDBs()
self.dump()
def getDBs(self):
if not self.dbs:
_out = subprocess.run([self.mysqlbin, '-BNne', 'SHOW DATABASES'],
stdout = subprocess.PIPE,
stderr = subprocess.PIPE)
if _out.returncode != 0:
raise RuntimeError(('Could not successfully list databases: '
'{0}').format(_out.stderr.decode('utf-8')))
self.dbs = _out.stdout.decode('utf-8').strip().splitlines()
return()
def dump(self):
for db in self.dbs:
args = copy.deepcopy(self.dumpopts)
outfile = os.path.join(self.outdir, '{0}.sql'.format(db))
if db in ('information_schema', 'performance_schema'):
args.append('--skip-lock-tables')
elif db == 'mysql':
args.append('--flush-privileges')
out = subprocess.run([self.mysqldumpbin,
'--result-file={0}'.format(outfile),
args,
db],
stdout = subprocess.PIPE,
stderr = subprocess.PIPE)
if out.returncode != 0:
warn = ('Error dumping {0}: {1}').format(db, out.stderr.decode('utf-8').strip())
warnings.warn(warn)
return()