BackupMySQLDatabases(Python)

Aus IT074-Wiki

Wechseln zu: Navigation, Suche

Download Backupdb.zip

#! /usr/bin/env python
import sys
import os
import getopt
import zipfile
import glob
from time import localtime, strftime, mktime
from ConfigParser import *
try:
	import MySQLdb as mydb 
except ImportError, err:
	print str(err) + '. Please install python-mysqldb. (http://sourceforge.net/projects/mysql-python) '
	sys.exit(-1)
 
def license():
	print """
	# Copyright (C) 2008 Tilo Werner \n \
	# \n \
	# backupdb is free software; you can redistribute it and/or modify \n \
	# it under the terms of the GNU General Public License as published by \n \
	# the Free Software Foundation; either version 2 of the License, or \n \
	# (at your option) any later version. \n \
	# \n \
	# backupdb is distributed in the hope that it will be useful, \n \
	# but WITHOUT ANY WARRANTY; without even the implied warranty of \n \
	# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the \n \
	# GNU General Public License for more details. \n \
	# \n \
	# You should have received a copy of the GNU General Public License \n \
	# along with backupdb; if not, write to the Free Software \n \
	# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  
	# USA \n """
	sys.exit(0)
 
def params():
    try:
        opts, extraparams = getopt.getopt(sys.argv[1:], "h:u:p:d:?vinf:", 
        		    ["host=", "user=", "password=","dbprefix=","help", 
        		    "license","verbose","include-system-dbs=","noquestions", 
        		    "file=", 'createconfigfile='])
    except getopt.GetoptError, err:
        print str(err)
        help()
        sys.exit(-1)
 
    # initial parameters
    param = {'host':'localhost','user':'root','passwd':'','dbprefix':'','file':'','outputdir':'./', \
	     'verbose':False,'includesystemdb':False,'noquestion':False}
 
    for o,p in opts:
     	if o in ['-f', '--file']:
		if p in ['?', 'help']:
			print 'Configfile syntax:\n'+helpfile()
			sys.exit(0)
		if not os.path.isfile(p) and p:
			quest = raw_input('No config file given, create one ? [Y/N] ')
			if quest.lower() == 'y':
				conffile = open(p,'w')
				warning = '#This is just an example default section, please modify it!\n\n'
	                	conffile.write(warning+helpfile().replace('\t','').replace('\\',''))
	                	conffile.close()
	                	print 'Initial configfile \"'+p+'\" written. Please modiy it.'
	                sys.exit(0)
		else:
			param['file'] = p
			return param
        if o in ['-h','--host']:
				param['host'] = p
        if o in ['-u','--user']:
                param['user'] = p
        if o in ['-p', '--password']:
				param['passwd'] = p
        if o in ['-d', '--dbprefix']:
	        	param['dbprefix'] = p
        if o in ['-v', '--verbose']:
				param['verbose'] = True
        if o in ['-i', '--include-system-dbs']:
				param['includesystemdb'] = True
        if o in ['-n', '--noquestions']:
                param['noquestion'] = True
        if o in ['-?', '--help']:
       			help()
        if o in ['--license']:
        		license()
    try:
        if os.path.isdir(extraparams[0]):
        	param['outputdir'] = str(extraparams[0])
        else:
        	print 'Please enter a valid destination directory'
        	help()
        	sys.exit(-1)
    except IndexError:
        print 'Wrong syntax'
        help()
        sys.exit(-1)
    return param
 
def help():
	print '\nbackupdb.py <options> destination directory\n'
	print \
	'\t -h --host \t\t Hostname (Default: localhost)\n\
    \t -u --user \t\t Username (Default: root)\n\
    \t -p --password \t\t Password (Default: empty)\n\
    \t -f --file \t\t Configfile, if non-existing is given, you will be ask to create one\n\
    \t\t\t\t For syntax type: '+sys.argv[0]+' -f/--file followed by ? or help\n\
    \t -n --noquestions \t I will not ask about anything, but take defaults \n\
    \t -i --include-system-dbs Include System Databases (Default exclude) \n\
    \t -v --verbose \t\t Mysqldump will speak to you (Default: nonverbose)\n\
    \t -? --help \t\t Prints what you see \n\
    \t --license \t\t Prints the license'
	sys.exit(0)
 
def helpfile():
	syntax = '\
	[DEFAULT]\n\
	host = localhost\n\
	user = root\n\
	passwd = None \n\
	maxdelcount = 5\n\
	maxdeltime = 10\n\
	dbprefix = Off\n\
	verbose = False\n\
	includesystemdb = 0\n\
	outputdir = ./\n\n\
	\#Uncomment the following and adapt it to your needs\n\n\
	\#[Your special context ...]\n\
	\#host = Your special Option\n\
	\#user = Your special Option\n\
	\#... overrides the defaults\n'
	return syntax
 
def error():
	if errorList:
		print '\nThe following problems occured:\n'
		for each in errorList:
			print each+'\n'
		sys.exit(-1)
	sys.exit(0)
 
def testlist(list,value):
	try:
		list.index(value)
		return True
	except:
		return False
def archive(argDict):
        _host = argDict['host']
        _outputdir = os.path.normpath(argDict['outputdir'])
        #_compression = argDict['compression']
        _compression = 8
#        try:
#	       	currentdumps = argDict['currentdumps']#
#	except KeyError:
#		currentdumps = ''
#		pass
        currenttime = strftime('%Y-%m-%d_%H-%M-%S',starttime)
        archivefile = _outputdir+'/'+_host+'/'+_host+'_'+currenttime+'.zip'
        sqlfilelist = list()
       	sqldellist = list()
        for each in glob.glob(_outputdir+'/'+_host+'/*.sql'):
        	if not each in currentdumps:
	        	sqlfilelist.append(each)
	if sqlfilelist:
	        try:
	                archivefileobject = zipfile.ZipFile(archivefile,'w',_compression)
	                for each in sqlfilelist:
	                	print 'Archiving '+each+' to '+archivefile
	                	archivefileobject.write(each, os.path.basename(each))
	                	sqldellist.append(each)
	                archivefileobject.close()
	                if not sqldellist:
	                	errorList.append('No files found for archiving '+archivefile)
	                	os.remove(archivefile)
	                elif not zipfile.is_zipfile(archivefile):
	                	errorList.append('Error creating zip archive '+archivefile)
	                        os.remove(archivefile)
	                else:
	                	for each in sqldellist:
	                		os.remove(each)
	        except error, err:
	                errorList.append(str(err))
	                pass
 
def clear(argDict):
        _host = argDict['host']
        _outputdir = os.path.normpath(argDict['outputdir'])
        zipfile = _outputdir+'/'+_host+'/*.zip'
        if argDict['maxdelcount'] not in negativelist:
	        _delcount = int(argDict['maxdelcount'])
	else:
		_delcount = 0
	if argDict['maxdeltime'] not in negativelist:
	        #_deltime = 24*3600*int(argDict['maxdeltime'])
	        _deltime = int(argDict['maxdeltime'])
	else:
		_deltime = 0
	# If both are false return with nothing done
	if not _delcount and not _deltime:
		return
        try:
        	filelist = list()
		dellist = list()
                for each in glob.glob(zipfile):
			filelist.append((os.stat(each)[8],each))
		del each
		# Sorting list form oldest to newest
		filelist.sort()
		# If the oldest file in list is younger then the _specified_ min. backup timeframe, do nothing.
		if (filelist[0][0] >= (mktime(starttime)-_deltime)) and _deltime:
			return
		# While more files in directory as specified as minimum file count 
		while len(filelist) > _delcount:
			# Mark oldest file(s) for deletion
			if filelist[0][0] <= (int(mktime(starttime)-_deltime)):
				dellist.append(filelist[0])
				filelist.remove(filelist[0])
			else:
				break
		if dellist:
			print 'Deleting expired archives'
			for each in dellist:
				os.remove(each[1])	
	except os.error, err:
                errorList.append('Error clearing the backupdir: '+_outputdir+'\n'+str(err))
 
def dump(argDict):
	_host = argDict['host']
	_user = argDict['user']
	_passwd = argDict['passwd']
	_dbprefix = argDict['dbprefix']
	_file = argDict['file']
	_outputdir = os.path.normpath(argDict['outputdir'])
	currenttime = strftime('%Y-%m-%d_%H-%M',starttime)
	if str(argDict['verbose']).lower() in positivelist:
		_verbose = '-v'
	else:
		_verbose = ''
	if str(argDict['includesystemdb']).lower() in positivelist:
		_includesystemdb = True
	else:
		_includesystemdb = False
	if str(argDict['noquestion']).lower() in positivelist:
		_noquestion = True
	else:
		_noquestion = False
	if str(argDict['passwd']).lower() in negativelist:
		_passwd = ''
	else:
		_passwd = argDict['passwd']
	if str(argDict['dbprefix']).lower() in negativelist:
		_dbprefix = ''
	else:
		_dbprefix = argDict['dbprefix']
 
	try:
		dbs = list()
		conn = mydb.connect(host=_host, user=_user, passwd=_passwd)
		cursor = conn.cursor()
		cursor.execute('show databases')
		for each in cursor.fetchall():
			dbs.append(each[0])
		del cursor
		conn.close()
		if not _includesystemdb:
			try:
				dbs.remove('information_schema')
				dbs.remove('mysql')
			except  ValueError:
				pass
		print 'Processing Host: '+_host
		for each in dbs:
			if each.startswith(_dbprefix):
				outputfile = _outputdir+'/'+_host+'/'+each+'_'+currenttime+'.sql'
				if not os.path.isdir(_outputdir+'/'+_host):
					os.makedirs(_outputdir+'/'+_host)
				print 'Dumping Database: '+each+' from '+_host+' to '+outputfile
				if _passwd:
					#print 'mysqldump '+_verbose+' -h '+_host+' -u '+ _user+' --password='+_passwd+' '+each+' > ' +outputfile
					open(outputfile, 'w').close()
					#os.system('mysqldump '+_verbose+' -h '+_host+' -u '+ _user+' --password='+_passwd+' '+each+' > '+outputfile)
				else:
					#print 'mysqldump '+_verbose+' -h '+_host+' -u '+ _user+' '+each+' > '+_outputfile
					open(outputfile, 'w').close()
					#os.system('mysqldump '+_verbose+' -h '+_host+' -u '+ _user+' '+each+' > '+outputfile)
				currentdump.append(each+'_'+currenttime+'.sql')
		if currentdump:
			argDict['currentdumps'] = currentdump
	except mydb.OperationalError, err:
		errorList.append('MySQL connect error for: ' +_user+'@'+_host+'\n'+str(err))
		pass
 
if __name__ == "__main__":
	errorList = list()
	argDict = params()
	starttime = localtime()
	_file = argDict['file']
	_dbprefix = argDict['dbprefix']
	_includesystemdb = argDict['includesystemdb']
	_noquestion = argDict['noquestion']
	positivelist = ['true','on','1']
	negativelist = ['false','off','0','null','none', '']
	currentdump = list()
	print ''
	if os.path.isfile(_file):
		try:
			config = ConfigParser()
			config.read(_file)
			for i in config.sections():
			archive(argDict)			
				configdict = dict(config.items(i))
				argDict['configsection'] = i
				for j,k in configdict.iteritems():
					argDict[j] = k
				dump(argDict)
				clear(argDict)
		except ConfigParser, err:
			print str(err)
		error()
 
	elif not _noquestion:
		if not _dbprefix:
			if _includesystemdb:
				quest = raw_input('No DB-Prefix given. Do you want to dump all databases, including system dbs? [Y/N] ')
			else:
				quest = raw_input('No DB-Prefix given. Do you want to dump all non-system databases? [Y/N] ')
			if quest.lower() == 'n':
				sys.exit(1)
		dump(argDict)
		error()