From a0921219c21c1b20256d4a661863761feaf8a423 Mon Sep 17 00:00:00 2001 From: Maxim Lihachev Date: Tue, 25 Jul 2017 12:48:40 +0500 Subject: [PATCH] service-updater v1.0 --- README.md | 28 ++++++++ config.ini | 35 ++++++++++ fabfile.py | 216 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ fabfile.pyc | Bin 0 -> 8303 bytes 4 files changed, 279 insertions(+) create mode 100644 README.md create mode 100644 config.ini create mode 100755 fabfile.py create mode 100644 fabfile.pyc 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 0000000000000000000000000000000000000000..999542c02d858e0a058340bff7f9187fb180ff54 GIT binary patch literal 8303 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