Скрипт на питоне для архивирования данных. С конфигом и ведением логов.

0. Возможности скрипта.
— архивация выбранных каталогов
— дамп выбранных баз mysql
— локальное копирование архивов
— копирование архивов через sftp на удаленную машину
— логирование операций

1. Системные требования.
— Установленный в системе интерпретатор языка питон. Данный скрипт написан под версию 2.7
— Библиотека paramiko, необходимая питону для работы с sftp.
Взять ее можно здесь www.paramiko.org



2. Конфигурационный файл скрипта.
По умолчанию скрипт берет конфигурацию из файла, находящегося с ним в одном каталоге. Имя файла по умолчанию — backup.cfg
Если есть необходимость запускать скрипт с разными файлами конфигурации то для этого достаточно указать в командной строке опцию -f и путь к файлу конфигурации.

2.1 Разделы конфигурационного файла. Опции и их значения.
Файл состоит из одного основного раздела, который называется [common] и содержит в себе основные настройки и дополнительных разделов содержащих в себе настройки заданий архивации данных. Дополнительных разделов может быть неограниченное количество.

-[common] содержит следующие опции:
log_name: имя файл с логами
log_path: путь к файлу с логами
mysqldump_path: путь к утилите для создания дампов mysql. Например: /usr/local/bin/mysqldump

-[дополнительный раздел] имеет произвольное название и содержит следующие опции:
type: тип задания. Значения: file — файловый бэкап, mysql — бэкап mysql
transfer_type: тип передачи бэкапа. Значение: fs — файловая система, sftp — трансфер через sftp. По умолчанию: fs.
dst_path: для всех бекапов, путь сохранения. Если нет — архив с бэкапом создается в сохраняемом каталоге
dst_file: имя архива с бэкапом, расширение tar.gz добавляется автоматом.
dst_file_postfix: дополнение имени архива с бэкапом. Значения: none — без дополнения, timestamp — дата и время создания архива. По умолчанию: none
src_path: для файлового бэкапа путь к каталогу который надо сохранять. При бэкапе mysql не требуется.
db_name: имя базы для бэкапа.
db_user: имя пользователя для бэкапа.
db_pass: пароль пользователя.
sftp_host: адрес хоста для трансфера при бэкапе через sftp.
sftp_port: порт хоста. По умолчанию — 22
sftp_user: имя пользователя для копирования по сфтп
sftp_pass: пароль пользователя.
sftp_path: путь к ресурсу доступному по sftp

Пример файла конфигурации
[common]
log_name:backup.log
log_path:
mysqldump_path:/usr/local/bin/mysqldump

[www_backup]
type:file
transfer_type:sftp
dst_path:/home/user/backup/
dst_file:www
dst_file_postfix:timestamp
src_path:/var/www/htdocs/cite/
sftp_host:192.168.1.102
sftp_port:22
sftp_user:user
sftp_pass:pass
sftp_path:/home/user/backup/

[db_mysql]
type:mysql
transfer_type:fs
dst_path:/home/user/backup/
dst_file_postfix:timestamp
db_name:db
db_user:root
db_pass:pass


3. Логирование операций
Логирование операций и ошибок во время их выполнения происходит в файл указанный в файле конфигурации. Если этого не сделано то по умолчанию файл с логами создается в каталоге запуска запуска скрипта и именем — backup.log

Фрагмент файла с логами
[27 October 2014 11:20:11] INFO : <----------SCRIPT IS RUNNING------->
[27 October 2014 11:20:11] INFO : start checking configuration for job [www_backup]
[27 October 2014 11:20:11] INFO : job [www_backup] accepted
[27 October 2014 11:20:11] INFO : --> job [www_backup] archiving of files completed
[27 October 2014 11:20:11] INFO : ----> files archived : 25
[27 October 2014 11:20:11] INFO : ----> bytes archived : 710946
[27 October 2014 11:20:11] INFO : ----> path to the archive : /home/user/backup/www_27-10-2014_11.20.11.tar.gz
[27 October 2014 11:20:11] INFO : start checking configuration for job [db_mysql]
[27 October 2014 11:20:11] INFO : job [db_mysql] accepted
[27 October 2014 11:20:12] INFO : --> job [db_mysql] mysql base [db] dumped
[27 October 2014 11:20:12] INFO : --> job [db_mysql] archiving of files completed
[27 October 2014 11:20:12] INFO : ----> files archived : 1
[27 October 2014 11:20:12] INFO : ----> bytes archived : 11465
[27 October 2014 11:20:12] INFO : ----> path to the archive : /home/user/backup/db_db_27-10-2014_11.20.11.tar.gz
[27 October 2014 11:20:12] INFO :  
[27 October 2014 11:20:23] INFO : <----------SCRIPT IS RUNNING------->
[27 October 2014 11:20:23] INFO : start checking configuration for job [www_backup]
[27 October 2014 11:20:23] INFO : job [www_backup] accepted
[27 October 2014 11:20:23] INFO : --> job [www_backup] archiving of files completed
[27 October 2014 11:20:23] INFO : ----> files archived : 25
[27 October 2014 11:20:23] INFO : ----> bytes archived : 710946
[27 October 2014 11:20:23] INFO : ----> path to the archive : /home/user/backup/www_27-10-2014_11.20.23.tar.gz
[27 October 2014 11:20:23] INFO : start checking configuration for job [db_mysql]
[27 October 2014 11:20:23] CRITICAL : sections [common] [mysqldump_path] verify the path to mysqldump
[27 October 2014 11:20:23] WARNING : job [db_mysql] reject
[27 October 2014 11:20:23] INFO :


4. Текст скрипта
#!/usr/local/bin/python2.7

import os
import sys
import subprocess
import ConfigParser
import datetime
import logging
import tarfile
import paramiko

#-------var-------
config_path = "backup.cfg"

#-------backup options var-------
src_path = ''
dst_file = ''
transfer_type = ''
dst_path = ''
dst_file_postfix = ''
sftp_host = ''
sftp_port = ''
sftp_user = ''
sftp_pass = ''
sftp_path = ''
db_name = ''
db_user = ''
db_pass = ''
mysqldump_path = ''
#--------------

def check_options(param_list, section, type_backup):

#-------f global var-------
    global src_path
    global dst_file
    global transfer_type
    global dst_path    
    global dst_file_postfix
    global sftp_host
    global sftp_port
    global sftp_user
    global sftp_pass
    global sftp_path
    global db_name
    global db_user
    global db_pass
    global mysqldump_path

#-------f var-------
    job_status = "accept"
    check = 0

#-------critical parametr's check-------
    if type_backup == "file":
        try:
            src_path = backup_cfg.get(section, 'src_path')
            if os.path.exists(src_path) == 0:
                job_status = "reject"
                logging.critical('[' + section + '] [src_path] : ' + src_path + ' not found')

            dst_file = backup_cfg.get(section, 'dst_file')
            if dst_file == "" :
                job_status = "reject"
                logging.critical('[' + section + '] [dst_file] check backup file name')

        except:
            logging.error(sys.exc_info()[1])
            job_status = "reject"

#-------secondary parametr's check-------
    try:
        transfer_type = backup_cfg.get(section, 'transfer_type')
        if transfer_type != 'fs' and transfer_type != 'sftp':
            logging.info('[' + section + '] [transfer_type] wrong value, set to default : fs')
            transfer_type = 'fs'
    except:
        err = ' '.join(sys.exc_info()[1])
        if (err.find('transfer_type') >= 0):
            logging.info(sys.exc_info()[1])
            logging.info('[' + section + '] [transfer_type] set to default value : fs')
            transfer_type = 'fs'

    try:
        dst_path = backup_cfg.get(section, 'dst_path')
        if os.path.exists(dst_path) == 0 and type_backup <> "mysql":
            logging.info('[' + section + '] [dst_path] path not found, set to [src_path] : [' + src_path + ']')
            dst_path = src_path
        elif os.path.exists(dst_path) == 0 and type_backup == "mysql":
            logging.info('[' + section + '] [dst_path] path not found, set to default : /tmp')
            dst_path = "/tmp/"
    except:
        err = ' '.join(sys.exc_info()[1])
        logging.info(sys.exc_info()[1])
        if (err.find('dst_path') >= 0) and type_backup <> "mysql":
            logging.info('[' + section + '] [dst_path] set to [src_path] : [' + src_path + ']')
            dst_path = src_path
        elif (err.find('dst_path') >= 0) and type_backup == "mysql":      
            logging.info('[' + section + '] [dst_path] set to /tmp')
            dst_path = "/tmp/"

    try:
        get_postfix = backup_cfg.get(section, 'dst_file_postfix')
        if get_postfix == 'timestamp':
            dst_file_postfix = "_" + datetime.datetime.now().strftime("%d-%m-%Y_%H.%M.%S")
        elif get_postfix =='none':
            dst_file_postfix = ''
        else:
            dst_file_postfix = ''
            logging.info('[' + section + '] [dst_file_postfix] wrong value, set to default : none')

    except:
        err = ' '.join(sys.exc_info()[1])
        if (err.find('dst_file_postfix') >= 0):
            logging.info(sys.exc_info()[1])
            logging.info('[' + section + '] [dst_file_postfix] set to default value : none')
            dst_file_postfix = ''

#-------sftp parametr's check-------
    if transfer_type == "sftp":
        try:
            sftp_port = backup_cfg.get(section, 'sftp_port')
            if sftp_port == "":
                logging.info('[' + section + '] [sftp_port] wrong value, set to default : 22')
                job_status = "accept"

            sftp_host = backup_cfg.get(section, 'sftp_host')
            if sftp_host == "":
                logging.critical('[' + section + '] [sftp_host] wrong value')
                job_status = "reject"

            sftp_user = backup_cfg.get(section, 'sftp_user')
            if sftp_user == "":
                logging.critical('[' + section + '] [sftp_user] wrong value')
                job_status = "reject"

            sftp_pass = backup_cfg.get(section, 'sftp_pass')
            if sftp_pass == "":
                logging.critical('[' + section + '] [sftp_pass] wrong value')
                job_status = "reject"

            sftp_path = backup_cfg.get(section, 'sftp_path')
            if sftp_path == "":
                logging.critical('[' + section + '] [sftp_path] wrong value')
                job_status = "reject"

        except:
            logging.critical(sys.exc_info()[1])
            job_status = "reject"

#-------mysql parametr check-------
    if type_backup == "mysql":
        try:
            db_name = backup_cfg.get(section, 'db_name')
            if db_name == "":
                logging.critical('[' + section + '] [db_name] wrong value')
                job_status = "reject"

            db_user = backup_cfg.get(section, 'db_user')
            if db_user == "":
                logging.critical('[' + section + '] [db_user] wrong value')
                job_status = "reject"

            db_pass = backup_cfg.get(section, 'db_pass')
            if db_pass == "":
                logging.critical('[' + section + '] [db_pass] wrong value')
                job_status = "reject"

            mysqldump_path = backup_cfg.get('common', 'mysqldump_path')
            if os.path.exists(mysqldump_path) == 0:
                logging.critical('sections [common] [mysqldump_path] verify the path to mysqldump')
                job_status = "reject"

        except:
            logging.critical(sys.exc_info()[1])
            job_status = "reject"
  
    return job_status

def backup(param_list, section, type_backup):

#-------f global var-------
    global src_path
    global dst_file
    global transfer_type
    global dst_path
    global dst_file_postfix
    global sftp_host
    global sftp_port
    global sftp_user
    global sftp_pass
    global sftp_path
    global db_name
    global db_user
    global db_pass
    global mysqldump_path
#--------------
    ret_base = check_options(param_list, section, type_backup)
    if ret_base == "accept" :
        backup_result = 0
        logging.info('job [' + section + '] accepted')

        if type_backup == "mysql":
            try:
                dst_file = db_name + "_db"
                src_path = dst_path + db_name + ".sql"
                dump_db = subprocess.Popen(mysqldump_path + " -u" + db_user + " -p" + db_pass + " " + db_name + " >" + src_path, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
                ret_dump = dump_db.wait()
                if ret_dump == 0:
                    backup_result = 0
                    logging.info('--> job [' + section + '] mysql base [' + db_name + '] dumped') 
                else:
                    backup_result = 1
                    for out in dump_db.stdout.readlines():
                        logging.error("----> " + out.strip())
                    logging.warning('job [' + section + '] aborted')
                
            except:
                backup_result = 1
                logging.error(sys.exc_info()[1])

        if backup_result == 0:
            try:
                arc_path = dst_path + dst_file + dst_file_postfix + ".tar.gz"
                backup_arc = tarfile.open(arc_path, "w:gz")
                backup_arc.add(src_path)
                size = 0
                count = 0
                for tarinfo in backup_arc:
                    size = size + tarinfo.size
                    count = count + 1
                backup_arc.close()
                if type_backup == "mysql":
                    os.remove(src_path)
                logging.info('--> job [' + section + '] archiving of files completed')
                logging.info('----> files archived : ' + str(count))
                logging.info('----> bytes archived : ' + str(size))
                logging.info('----> path to the archive : ' + arc_path)

#-------sftp transfer if enable-------
                if transfer_type == "sftp":
                    try:
                        transfer_cnt = datetime.datetime.now()
                        transport = paramiko.Transport((sftp_host, int(sftp_port)))
                        transport.connect(username = sftp_user, password = sftp_pass)
                        sftp = paramiko.SFTPClient.from_transport(transport)
                        local_file = arc_path
                        remote_path = sftp_path + dst_file + dst_file_postfix + ".tar.gz"
                        sftp_res = sftp.put(local_file, remote_path)
                        sftp.close()
                        transport.close()                
                        transfer_cnt = datetime.datetime.now() - transfer_cnt
                        logging.info('--> job [' + section + '] sftp file transfer finished')
                        logging.info('----> bytes transfer : ' + str(sftp_res.st_size))
                        logging.info('----> duration of the file transfer : ' + str(transfer_cnt.seconds) + ' seconds')
                    except:
                        logging.error(sys.exc_info()[1])
            except:
                logging.error(sys.exc_info()[1])

    elif ret_base == "reject" :
        logging.warning('job [' + section + '] reject')

#-------check command line options-------
if len(sys.argv) > 2 and sys.argv[1] == '-f':
    if os.path.exists(sys.argv[2]) == 0:
        print 'config not found'
    else:
        config_path = sys.argv[2]

#-------load config-------
backup_cfg = ConfigParser.RawConfigParser()
ret = backup_cfg.read(config_path)
sections_lst = backup_cfg.sections()

#-------check common options-------
try:
    log_name = backup_cfg.get('common', 'log_name')
    if log_name == "":
        log_name = "backup.log"
except:
    log_name = "backup.log"

try:
    log_path = backup_cfg.get('common', 'log_path')
    if os.path.exists(log_path) == 0:
        log_path = ""
except:
    log_path = ""    

logging.basicConfig(filename = log_path + log_name ,format='[%(asctime)s] %(levelname)s : %(message)s', datefmt='%d %B %Y %H:%M:%S', level=logging.INFO)
logging.info('<----------SCRIPT IS RUNNING------->')

#-------general-------
for section_name in sections_lst:
    if section_name <> "common":
        try:
            logging.info('start checking configuration for job [' + section_name + ']')    
            type_backup = backup_cfg.get(section_name, 'type')
            value_list = backup_cfg.items(section_name)
            if type_backup == "file" or type_backup == "mysql":
                backup(value_list, section_name, type_backup)
            else:
                logging.critical('[' + section_name + '] [type] wrong value')
                logging.warning('job [' + section_name + '] reject')
        except:
            logging.error(sys.exc_info()[1])
            logging.warning('job [' + section_name + '] reject')
logging.info(' ')


И ссылка на pastebin, на всяк случай
pastebin.com/Mcmm8vqu

0 комментариев

Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.