commit a0921219c21c1b20256d4a661863761feaf8a423 Author: Maxim Lihachev Date: Tue Jul 25 12:48:40 2017 +0500 service-updater v1.0 diff --git a/README.md b/README.md new file mode 100644 index 0000000..14fc13c --- /dev/null +++ b/README.md @@ -0,0 +1,28 @@ +**service-updater** - Script for updating service/application + +## Description + +Scenario update application on remote server with full back up of configuration +files and database. + +## Requirements + + # install python-dev libffi-dev + # pip install configparser + # pip install fabric + +## Usage + + backup_db Back up distributive + backup_db_copy Backround upload of backup to remote server + backup_files Back up files + copy_config Copy configuration files + copy_custom_conf Upload custom.conf to remote server + copy_libs Copy libs + diff_config Comparison of configuration files + make_dist Make distributive (play dist) + start Start service + stop Stop service + update Update service + upload_dist Upload distributive to remote server + diff --git a/config.ini b/config.ini new file mode 100644 index 0000000..5e225d6 --- /dev/null +++ b/config.ini @@ -0,0 +1,35 @@ +[DEFAULT] + +config_dirs: [['daemon/root/conf/', 'conf/'], ['daemon/root/modules/catalogs/', 'catalogs/'] + +config_exclude: ['custom.conf', 'custom.conf.template'] + +[java] +play: play + +[release] +app_path_stand: /opt/daemon-rc +app_path_dist: ${release:app_path_stand}/daemon/root/target/universal + +make_dist_script: make_dist.sh + +custom_conf_template: ${release:app_path_stand}/daemon/root/conf/custom.conf.template + +[product] +host: user@127.0.0.1:10022 + +app_path_stand: /root/daemon/ +app_path_dist: ${product:app_path_stand}/../dist +app_path_libs: ${product:app_path_stand}/daemon/root/lib + +app_pid_file: ${product:app_path_stand}/daemon/root/RUNNING_PID + +app_start_script: start_app.sh + +backup_files: ['catalogs', 'daemon/catalogs', 'daemon/root/lib', 'conf'] + +backup_db_script: /root/scripts/db-backup.sh +backup_db_copy_script: /root/scripts/db-backup-upload.sh + +custom_conf_template: ${product:app_path_stand}/conf/custom.conf.template + diff --git a/fabfile.py b/fabfile.py new file mode 100755 index 0000000..2d6bd51 --- /dev/null +++ b/fabfile.py @@ -0,0 +1,216 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# +# Script for updating service/application on remote server using Fabric. +# +# (c) envrm +# + +#Installing fabric: +# apt-get install python-dev libffi-dev +# pip install configparser +# pip install fabric + +from __future__ import with_statement +from fabric.api import * + +import os +import ast +import glob +import datetime +import subprocess +import configparser + +from contextlib import contextmanager + +#----------------------------------------------------------------------------- +# Class for errors catching +class FabricException(Exception): + pass + +#----------------------------------------------------------------------------- + +#Color highlighting of errors +env.colorize_errors = True +#Generating exceprions instead fails +env.abort_exception=FabricException + +#----------------------------------------------------------------------------- + +@contextmanager +def if_needed(): + '''Execution with ignore errors''' + try: + yield + except: + pass + +def readConfig(config_file): + '''Read config file''' + global settings + settings = configparser.ConfigParser() + settings._interpolation = configparser.ExtendedInterpolation() + settings.read(config_file) + return settings + +@contextmanager +def timestamp(*args): + print '[' + current_time() + ']', args + yield + +def opt(parameter): + '''Get options''' + setting = parameter.split(":") + return settings.get(setting[0], setting[1]) + +def background_run(command): + '''Background execution of commands on remote servers''' + subprocess.Popen(["nohup", command]) + +def current_time(): + '''Current time in HH:MM:SS format''' + return datetime.datetime.now().time().strftime('%X') + +#----------------------------------------------------------------------------- + +@task +def make_dist(): + '''Make distributive (play dist)''' + with lcd(opt('release:app_path_stand')): + local("sh ./%s" % opt('release:make_dist_script')) + +@task +def diff_config(): + '''Comparison of configuration files''' + old_config = opt('product:custom_conf_template') + new_config = opt('release:custom_conf_template') + tmp_config = os.path.join('/tmp/', os.path.basename(old_config)) + + with hide('commands'): + get(old_config, '/tmp/') + + diff = os.system("diff -w -B %s %s" % (tmp_config, new_config)) + + local("rm -f %s" % tmp_config) + + if diff == 0: + return False + else: + return True + +def copy_dirs(directories): + '''Upload local directories to remote server''' + for src_dir, dst_dir in ast.literal_eval(directories): + for config_file in glob.glob(src_dir + "/*"): + if os.path.basename(config_file) not in opt('product:config_exclude'): + src_root = src_dir.split('*')[0] + config_file_path = config_file.replace(src_root, '') + remote_file = os.path.join(opt('product:app_path_stand'), dst_dir.split('*')[0], config_file_path) + + put(config_file, remote_file) + +@task +def upload_dist(): + '''Upload distributive to remote server''' + dist_dir = os.path.join(opt('product:app_path_dist'), date) + run('mkdir -p %s' % dist_dir) + + #Uploading archive + with lcd(opt('release:app_path_dist')): + put('*.zip', dist_dir) + + #Extracting files + with cd(dist_dir): + run('yes All | unzip *.zip') + +@task +def copy_config(): + '''Copy configuration files''' + if diff_config(): + print "#### [WARN] Configuration files on the servers are different. Check them manually" + print "#### or execute `fab copy_custom_conf' for force upload custom.conf.template" + exit(1) + + copy_dirs(opt('release:config_dirs')) + +@task +def copy_custom_conf(): + '''Upload custom.conf to remote server''' + put(opt('release:custom_conf_template'), opt('product:custom_conf_template')) + +@task +def backup_files(): + '''Back up files''' + with cd(opt('product:app_path_stand')): + run('mkdir -p backup/%s' % date) + for to_backup in ast.literal_eval(opt('product:backup_files')): + run('cp -rv %s ../backup/%s' % (os.path.join(opt('product:app_path_stand'), to_backup), date)) + +@task +def backup_db(): + '''Back up distributive''' + with cd(opt('product:app_path_stand')): + run("%s" % opt('product:backup_db_script')) + +@task +def backup_db_copy(): + '''Backround upload of backup to remote server''' + with cd(opt('product:app_path_stand')): + background_run(opt('product:backup_db_copy_script')) + +@task +def stop(): + '''Stop service''' + run('kill $(cat %s)' % opt('product:app_pid_file')) + +def remove_libs(): + '''Remove previous version''' + run('rm -rfv %s' % opt('product:app_path_libs')) + +@task +def copy_libs(): + '''Copy libs''' + with cd(opt('product:app_path_stand')): + run('cp -rv %s %s' % (os.path.join(opt('product:app_path_dist'), date, '*/lib'), opt('product:app_path_libs'))) + +@task +def start(): + '''Start service''' + with cd(opt('product:app_path_stand')): + run("sh %s >> nohup.out 2>> nohup.out < /dev/null &" % opt('product:app_start_script'), pty=True) + +#----------------------------------------------------------------------------- +@task +def update(): + '''Update service''' + make_dist() + upload_dist() + copy_config() + backup_files() + + with timestamp("MAKING BACKUP"): + backup_db() + with timestamp("UPLOADING BACKUP"): + backup_db_copy() + with timestamp("NEXT STEP"): + pass + + with if_needed(): stop() + + remove_libs() + copy_libs() + + start() + +#----------------------------------------------------------------------------- + +settings = [] + +readConfig('config.ini') + +#Remote host +env.host_string = opt('product:host') + +date = datetime.datetime.now().strftime("%Y-%m-%d") + diff --git a/fabfile.pyc b/fabfile.pyc new file mode 100644 index 0000000..999542c Binary files /dev/null and b/fabfile.pyc differ