commit 9c73dcfcb448b49641685796ed40bfd1e0c6f86c Author: Maxim Lihachev Date: Fri Aug 4 12:40:30 2017 +0500 java-app-updater v1.0 diff --git a/README.md b/README.md new file mode 100644 index 0000000..2a363c4 --- /dev/null +++ b/README.md @@ -0,0 +1,27 @@ +**service-updater** - Script for upgrading java/play application from git + +## Description + +Scenario upgrade java/play application in production directory + +## Requirements + + CentOS: yum -y install python-pip + Debian: apt-get install python-pip + + pip install configparser + +## Usage + +service.py [service] + + start - Start service + stop - Stop service + restart - Restart service + build - Build service. [stand|modules|all] + update - Update current service from repository + upgrade - Update developing service from repository + +To build a specific service you need to pass a parameter [service]: + ./service.py application build all + diff --git a/config.ini b/config.ini new file mode 100644 index 0000000..7ae249b --- /dev/null +++ b/config.ini @@ -0,0 +1,68 @@ +[service] + +projects: {'application': 'app', 'daemon': 'srv'} + +default: application + +#Default parameters can be used in other sections. +[DEFAULT] + +java_options: -Xmx2g + +path: ${home}/$$SERVICE +bin: ${path}/target/universal/stage/bin/$$SERVICE +pid: ${path}/target/universal/stage/RUNNING_PID + +modules_dir: ${home}/modules/ +modules_list: ${modules_dir}/modules.list + +class_path: ${path}/target/universal/stage/lib/ +git_path: ${home}/git/ + +#clean all +targets: ${path}/modules/*/target + +config_file_path: ${path}/conf/application.conf + +restarts_log: ${home}/restarts.log +update_log: ${home}/update.log +build_log_file: ${home}/build.log + +server: play.core.server.ServerStart + +http_port: 10000 + +#Java options +play: activator -Duser.home=${home}/user_home -Dsbt.ivy.home=${home}/user_home/repository -Divy-home=${home}/user_home/repository + +modules_log: ${home}/modules.updated +sbt_config: @${home}/sbt.boot.properties +sbt_opts: -Dsbt.boot.directory=${home}/user_home/.sbt + +[build] + +java_options: -J-Xms2g -J-Xmx4g + +git_branch: dev + +#Current directory +[develop] + +home: PATH/TO/DIRECTORY +git_path: ${home} + +[app] + +home: PATH +path: ${home}/git/app + +http_port: 10001 + +[srv] + +home: PATH +path: ${home}/git/srv +class_path: ${path}/target/universal/stage/lib/ + +http_port: 10002 + diff --git a/service.py b/service.py new file mode 100755 index 0000000..b4ceb65 --- /dev/null +++ b/service.py @@ -0,0 +1,265 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# +# Script for upgrading java/play application from git +# +# (c) envrm +# + +#Requirements +# +# CentOS: yum -y install python-pip +# Debian: apt-get install python-pip +# +# pip install configparser +# + +import re +import os +import ast +import sys +import shutil +import signal +import datetime +import subprocess +import configparser + +from string import Template +from contextlib import contextmanager + +@contextmanager +def if_needed(): + '''Execute with error ignoring''' + try: + yield + except: + pass + +class Dir: + '''Execute procedures in directory''' + def __init__(self, directory): + self.current_dir = directory + + def __enter__(self): + self.previous_dir = os.getcwd() + os.chdir(self.current_dir) + + def __exit__(self, type, value, traceback): + os.chdir(self.previous_dir) + +#----------------------------------------------------------------------- + +def readConfig(config_file): + '''Loading configuration file''' + global settings + settings = configparser.ConfigParser() + settings._interpolation = configparser.ExtendedInterpolation() + settings.read(config_file) + return settings + +def opt(parameter): + '''Getting parameters from file''' + setting = parameter.split(":") + value = settings.get(setting[0], setting[1]) + + variables = re.findall(r"\$(\w+)", value) + + substr = {v: globals()[v] for v in variables if v in globals()} + + return Template(value).safe_substitute(**substr) + +def saveTimestamp(outfile, mode="a"): + '''Saving timestamt to file''' + with open(outfile, mode) as log: + now = datetime.datetime.now() + log.write(now.strftime("%Y-%m-%d %H:%M:%S\n")) + +#----------------------------------------------------------------------- + +def git(command, stand="develop"): + '''Executing git command''' + with Dir(opt(stand + ':git_path')): + git_output = subprocess.check_output('git ' + command, shell=True) + + return git_output + +def stop(stand="develop"): + '''Stop service''' + if os.path.isfile(opt(stand + ':pid')): + pid = opt(stand + ':pid') + else: + pid = os.path.join(opt(stand + ':path'), 'RUNNING_PID') + + print pid + with if_needed(): + with open(pid, 'r') as pid_file: + os.kill(int(pid_file.read()), signal.SIGTERM) + os.remove(pid) + print "#### Application stopped" + +def start(stand): + '''Start service''' + service_dir = opt(stand + ':path') + server = opt(stand + ':server') + + start_cmd = 'nohup java ' + opt(stand + ':java_options') \ + + ' -Dfile.encoding=UTF-8 -Dconfig.file=' + service_dir + '/conf/application.conf ' \ + + ' -Dhttp.port=' + opt(stand + ':http_port') + ' -cp "' + service_dir + '/conf/:' + opt(stand + ':class_path') + '/*" ' \ + + server + ' ' + service_dir + ' | tee -a ' + opt(stand + ':home') + '/nohup.out &' + + print start_cmd + + with Dir(service_dir): + return_code = os.system(start_cmd) + if return_code == 0: + print "#### Application started" + else: + print return_code + exit(1) + +def restart(stand="develop"): + '''Restart service''' + stop(stand) + start(stand) + saveTimestamp(opt(stand + ':restarts_log')) + +def clean_all(): + '''Remove artefacts''' + os.system("rm -rfv " + opt('develop:targets')) + +def build(target="stand", modules=None): + '''Build service. [stand|modules|all]''' + play_cmd = opt(SERVICE + ':play') + " " + opt(SERVICE + ':sbt_config') + + print "OK" + + if target == "modules" or target == "all": + with Dir(opt('develop:modules_dir')): + if modules == None: + modules = os.walk(".").next()[1] + + modules = filter(None, set(modules)) + + with open(opt('develop:modules_list')) as f: + modules_deps = f.read().splitlines() + + for mod in modules_deps: + if mod in modules: + with Dir(os.path.join(opt('develop:modules_dir'), mod)): + print "#### Update module " + mod + return_code = os.system(play_cmd + " publishLocal") + + if return_code != 0: + exit(1) + + if target == "stand" or target == "all": + print "#### Update application" + with Dir(opt('develop:path')): + with if_needed(): + shutil.rmtree("target") + print play_cmd + " " + opt('build:java_options') + " stage | tee " + opt('develop:build_log_file') + return_code = os.system(play_cmd + " " + opt('build:java_options') + " stage | tee " + opt('develop:build_log_file')) + + if return_code != 0: + exit(1) + +def update_from_git(): + '''Update app with modules''' + git('checkout ' + opt('build:git_branch')) + git('branch --set-upstream-to=origin/' + opt('build:git_branch') + ' ' + opt('build:git_branch')) + print git('fetch') + git_log = git('diff --name-only HEAD..origin/' + opt('build:git_branch')) + git('pull') + + modules = [] + for line in git_log.splitlines(): + module = re.findall(r'commonmodules/([^/]*)/.*', line) + if module: + modules.extend(module) + + build("all", modules) + +def copy_libs(): + '''Copy libraries''' + with if_needed(): + shutil.rmtree(opt(SERVICE + ':class_path')) + shutil.copytree(opt('develop:class_path'), opt(SERVICE + ':class_path')) + +def update(): + '''Update current service from repository''' + saveTimestamp(opt('develop:restarts_log')) + stop() + update_from_git() + start() + saveTimestamp(opt('develop:update_log'), 'w') + +def upgrade(): + '''Update developing service from repository''' + update_from_git() + saveTimestamp(opt(SERVICE + ':restarts_log')) + stop(SERVICE) + git("pull", SERVICE) + copy_libs() + start(SERVICE) + saveTimestamp(opt(SERVICE + ':update_log'), 'w') + +#----------------------------------------------------------------------- + +def show_help(): + exe = os.path.basename(sys.argv[0]) + + '''Show help''' + print "USAGE: " + exe + " [service] <" + "|".join(allowed_tasks) + ">" + + for task in allowed_tasks: + print "\t" + task + "\t- " + globals()[task].__doc__ + + print "\nTo build a specific service you need to pass a parameter [service]:" + print "\t./" + exe, opt('service:default'), "\b build all" + print "\n[service] can be one of «" + "», «".join(ast.literal_eval(opt('service:projects')).keys()) + "»." + print "\nDefault value of [service] is", opt('service:default'), "\n" + +settings = [] + +readConfig('config.ini') + +allowed_tasks = ['start', 'stop', 'restart', 'build', 'update', 'upgrade'] + +#Services allowed for building +services = ast.literal_eval(opt('service:projects')) + +#Default service +SERVICE = ast.literal_eval(opt('service:projects'))[opt('service:default')] + +#Command line arguments parsing +if len(sys.argv) > 2 and sys.argv[2] in allowed_tasks: + arg_index = 2 + if sys.argv[1] in services: + SERVICE = ast.literal_eval(opt('service:projects'))[sys.argv[1]] +elif len(sys.argv) > 1 and sys.argv[1] in allowed_tasks: + arg_index = 1 +else: + show_help() + exit(1) + +#Procedure for execution +task = globals()[sys.argv[arg_index]] + +if task: + target = { + 'update' : 'develop', + 'upgrade': 'mroot' + }.get(sys.argv[1], "develop") + + if len(sys.argv) > 2 and sys.argv[2] in ['start','stop','restart']: + SERVICE = ast.literal_eval(opt('service:projects'))[sys.argv[1]] + print "CMD: " + sys.argv[1] + " " + sys.argv[2], "| SERVICE: ", SERVICE + task(SERVICE) + elif len(sys.argv) > (arg_index + 1): + print "CMD: " + sys.argv[arg_index] + " " + sys.argv[arg_index + 1] + task(sys.argv[arg_index + 1]) + else: + print "CMD: " + sys.argv[arg_index] + task() +