From c0dbfce4c2121c7347408c582a5c85ba3977262d Mon Sep 17 00:00:00 2001 From: Christophe Mehay Date: Fri, 30 Dec 2016 19:52:12 +0100 Subject: [PATCH] Add post_run_commands --- .travis.yml | 12 +++++++++ CHANGELOG | 2 ++ Makefile | 13 ++++++++-- pyentrypoint/command.py | 9 +++++++ pyentrypoint/config.py | 22 ++++++++++++++++ pyentrypoint/entrypoint.py | 45 ++++++++------------------------- pyentrypoint/runner.py | 52 ++++++++++++++++++++++++++++++++++++++ requirements-dev.txt | 24 +----------------- setup.py | 2 +- tests/configs/runner.yml | 5 ++++ tests/runner_test.py | 25 ++++++++++++++++++ 11 files changed, 150 insertions(+), 61 deletions(-) create mode 100644 .travis.yml create mode 100644 CHANGELOG create mode 100644 pyentrypoint/runner.py create mode 100644 tests/configs/runner.yml create mode 100644 tests/runner_test.py diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..a59de82 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,12 @@ +sudo: required + +services: + - docker + +language: python + +before_install: + - pip install -r requirements-dev.txt + +script: + - make test diff --git a/CHANGELOG b/CHANGELOG new file mode 100644 index 0000000..c600484 --- /dev/null +++ b/CHANGELOG @@ -0,0 +1,2 @@ +v0.5.0: + - add post_run_commands in entrypoint-config.yml diff --git a/Makefile b/Makefile index 2b64177..7e1fadc 100644 --- a/Makefile +++ b/Makefile @@ -3,8 +3,17 @@ build: @docker-compose build -test: build - @docker-compose up --force-recreate testpython2 testpython3 +clean: + docker-compose down --remove-orphans + +test: build test-python2 test-python3 clean + +test-python2: + @docker-compose run --rm testpython2 + +test-python3: + @docker-compose run --rm testpython3 + test_debug: build @docker-compose up --force-recreate testpython2_debug testpython3_debug diff --git a/pyentrypoint/command.py b/pyentrypoint/command.py index 1173d12..7c1c5c4 100644 --- a/pyentrypoint/command.py +++ b/pyentrypoint/command.py @@ -6,6 +6,7 @@ import os from fnmatch import fnmatch from .logs import Logs +from .runner import Runner class Command(object): @@ -21,6 +22,8 @@ class Command(object): self.log.debug('Arguments are: "{args}"'.format( args='" "'.join(self.args) )) + self.runner = Runner(config=self.config, + cmds=self.config.post_run_commands) def _rm_dockerenv(self): """Delete '/.dockerenv' and '/.dockerinit' files""" @@ -88,6 +91,11 @@ class Command(object): self.log.debug('Launching reloader process') self.config.reload.run_in_process() + def _run_post_commands(self): + if self.config.post_run_commands: + self.log.debug('Running post run commands') + self.runner.run_in_process() + def run(self): if not self.is_handled: self._exec() @@ -107,4 +115,5 @@ class Command(object): [p for p in subcom if fnmatch(self.args[0], p)]: self.args.insert(0, self.command) self._run_reloader() + self._run_post_commands() self._exec() diff --git a/pyentrypoint/config.py b/pyentrypoint/config.py index e6c7340..db5c45e 100644 --- a/pyentrypoint/config.py +++ b/pyentrypoint/config.py @@ -203,6 +203,11 @@ class Config(ConfigMeta): """Return list of postconf commands""" return self._return_item_lst('post_conf_commands') + @property + def post_run_commands(self): + """Return list of post run commands""" + return self._return_item_lst('post_run_commands') + @property def reload(self): """Return Reloader object if reload is set""" @@ -239,3 +244,20 @@ class Config(ConfigMeta): if 'ENTRYPOINT_QUIET' in os.environ: return True return bool(self._config.get('quiet', False)) + + @property + def is_disabled(self): + """Return if service is disabled using environment""" + return 'ENTRYPOINT_DISABLE_SERVICE' in os.environ + + @property + def should_config(self): + """Check environment to tell if config should apply anyway""" + return 'ENTRYPOINT_FORCE' in os.environ + + @property + def raw_output(self): + """Check if command output should be displayed using logging or not""" + if 'ENTRYPOINT_RAW' in os.environ: + return True + return bool(self._config.get('raw_output', False)) diff --git a/pyentrypoint/entrypoint.py b/pyentrypoint/entrypoint.py index d77e368..7a096a8 100644 --- a/pyentrypoint/entrypoint.py +++ b/pyentrypoint/entrypoint.py @@ -8,11 +8,8 @@ from __future__ import unicode_literals import json import os -from subprocess import PIPE -from subprocess import Popen from sys import argv from sys import exit -from sys import stdout import yaml from jinja2 import Environment @@ -22,6 +19,7 @@ from .config import Config from .constants import ENTRYPOINT_FILE from .docker_links import DockerLinks from .logs import Logs +from .runner import Runner __all__ = ['Entrypoint', 'main'] @@ -49,6 +47,7 @@ class Entrypoint(object): if self.config.quiet: Logs.set_critical() self.args = args + self.runner = Runner(config=self.config) @property def is_handled(self): @@ -58,21 +57,21 @@ class Entrypoint(object): @property def is_disabled(self): """Return if service is disabled using environment""" - return 'ENTRYPOINT_DISABLE_SERVICE' in os.environ + return self.config.is_disabled @property def should_config(self): """Check environment to tell if config should apply anyway""" - return 'ENTRYPOINT_FORCE' in os.environ + return self.config.should_config @property def raw_output(self): """Check if command output should be displayed using logging or not""" - return 'ENTRYPOINT_RAW' in os.environ + return self.config.raw_output def exit_if_disabled(self): """Exist 0 if service is disabled""" - if not self.is_disabled: + if not self.config.is_disabled: return self.log.warning("Service is disabled by 'ENTRYPOINT_DISABLE_SERVICE' " @@ -94,41 +93,17 @@ class Entrypoint(object): yaml=yaml, containers=DockerLinks().to_containers())) - def run_conf_cmd(self, cmd): - self.log.debug('run command: {}'.format(cmd)) - proc = Popen(cmd, shell=True, stdout=PIPE, stderr=PIPE) - out, err = proc.communicate() - - def dispout(output, cb): - enc = stdout.encoding or 'UTF-8' - output = output.decode(enc).split('\n') - l = len(output) - for c, line in enumerate(output): - if c + 1 == l and not len(line): - # Do not display last empty line - break - cb(line) - - if out: - display_cb = self.log.info if not self.raw_output else print - dispout(out, display_cb) - if err: - display_cb = self.log.warning if not self.raw_output else print - dispout(err, display_cb) - if proc.returncode: - raise Exception('Command exit code: {}'.format(proc.returncode)) - def run_pre_conf_cmds(self): for cmd in self.config.pre_conf_commands: - self.run_conf_cmd(cmd) + self.runner.run_cmd(cmd) if 'ENTRYPOINT_PRECONF_COMMAND' in os.environ: - self.run_conf_cmd(os.environ['ENTRYPOINT_PRECONF_COMMAND']) + self.runner.run_cmd(os.environ['ENTRYPOINT_PRECONF_COMMAND']) def run_post_conf_cmds(self): for cmd in self.config.post_conf_commands: - self.run_conf_cmd(cmd) + self.runner.run_cmd(cmd) if 'ENTRYPOINT_POSTCONF_COMMAND' in os.environ: - self.run_conf_cmd(os.environ['ENTRYPOINT_POSTCONF_COMMAND']) + self.runner.run_cmd(os.environ['ENTRYPOINT_POSTCONF_COMMAND']) def launch(self): self.config.command.run() diff --git a/pyentrypoint/runner.py b/pyentrypoint/runner.py new file mode 100644 index 0000000..298020f --- /dev/null +++ b/pyentrypoint/runner.py @@ -0,0 +1,52 @@ +"Run commands" +from __future__ import absolute_import +from __future__ import print_function +from __future__ import unicode_literals + +from multiprocessing import Process +from subprocess import PIPE +from subprocess import Popen +from sys import stdout + +from .logs import Logs + + +class Runner(object): + 'This object run commands' + + def __init__(self, config, cmds=[]): + self.log = Logs().log + self.cmds = cmds + self.raw_output = config.raw_output + + def run_cmd(self, cmd): + self.log.debug('run command: {}'.format(cmd)) + proc = Popen(cmd, shell=True, stdout=PIPE, stderr=PIPE) + out, err = proc.communicate() + + def dispout(output, cb): + enc = stdout.encoding or 'UTF-8' + output = output.decode(enc).split('\n') + l = len(output) + for c, line in enumerate(output): + if c + 1 == l and not len(line): + # Do not display last empty line + break + cb(line) + + if out: + display_cb = self.log.info if not self.raw_output else print + dispout(out, display_cb) + if err: + display_cb = self.log.warning if not self.raw_output else print + dispout(err, display_cb) + if proc.returncode: + raise Exception('Command exit code: {}'.format(proc.returncode)) + + def run(self): + for cmd in self.cmds: + self.run_cmd(cmd) + + def run_in_process(self): + self.proc = Process(target=self.run) + self.proc.start() diff --git a/requirements-dev.txt b/requirements-dev.txt index 619fbf6..7590a43 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,23 +1 @@ -argparse==1.4.0 -aspy.yaml==0.2.1 -blessings==1.6 -bpython==0.14.2 -cached-property==1.3.0 -colorlog==2.6.1 -curtsies==0.1.23 -docker-compose==1.5.2 -docker-py==1.6.0 -dockerpty==0.3.4 -docopt==0.6.2 -greenlet==0.4.9 -jsonschema==2.5.1 -nodeenv==0.13.6 -ordereddict==1.1 -pre-commit==0.7.5 -Pygments==2.0.2 -PyYAML==3.11 -requests==2.7.0 -six==1.10.0 -texttable==0.8.4 -virtualenv==13.1.2 -websocket-client==0.35.0 +docker-compose==1.7.0 diff --git a/setup.py b/setup.py index ad6e360..d50a6ff 100644 --- a/setup.py +++ b/setup.py @@ -5,7 +5,7 @@ from setuptools import setup # Thanks Sam and Max -__version__ = '0.4.7' +__version__ = '0.5.0' if __name__ == '__main__': setup( diff --git a/tests/configs/runner.yml b/tests/configs/runner.yml new file mode 100644 index 0000000..55ab63d --- /dev/null +++ b/tests/configs/runner.yml @@ -0,0 +1,5 @@ +post_run_commands: + - sleep 2 + - echo 'OK' > /tmp/runner_test + +command: sleep diff --git a/tests/runner_test.py b/tests/runner_test.py new file mode 100644 index 0000000..113f671 --- /dev/null +++ b/tests/runner_test.py @@ -0,0 +1,25 @@ +"Tests for runner" +from __future__ import absolute_import +from __future__ import unicode_literals + +import os +from multiprocessing import Process + +from pyentrypoint import Entrypoint + + +def test_runner(): + run = [ + (Process(target=Entrypoint( + conf='configs/runner.yml', + args=['sleep', '5']).launch), + '/tmp/runner_test', 0, 0), + ] + + for proc, test, uid, gid in run: + proc.start() + proc.join() + with open(test, 'r') as f: + assert f.readline().startswith('OK') + assert os.stat(test).st_uid == uid + assert os.stat(test).st_gid == gid