2
0
mirror of https://github.com/cmehay/pyentrypoint synced 2024-10-30 15:21:11 +00:00

Add post_run_commands

This commit is contained in:
Christophe Mehay 2016-12-30 19:52:12 +01:00
parent 72e44abe8c
commit c0dbfce4c2
11 changed files with 150 additions and 61 deletions

12
.travis.yml Normal file
View File

@ -0,0 +1,12 @@
sudo: required
services:
- docker
language: python
before_install:
- pip install -r requirements-dev.txt
script:
- make test

2
CHANGELOG Normal file
View File

@ -0,0 +1,2 @@
v0.5.0:
- add post_run_commands in entrypoint-config.yml

View File

@ -3,8 +3,17 @@
build: build:
@docker-compose build @docker-compose build
test: build clean:
@docker-compose up --force-recreate testpython2 testpython3 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 test_debug: build
@docker-compose up --force-recreate testpython2_debug testpython3_debug @docker-compose up --force-recreate testpython2_debug testpython3_debug

View File

@ -6,6 +6,7 @@ import os
from fnmatch import fnmatch from fnmatch import fnmatch
from .logs import Logs from .logs import Logs
from .runner import Runner
class Command(object): class Command(object):
@ -21,6 +22,8 @@ class Command(object):
self.log.debug('Arguments are: "{args}"'.format( self.log.debug('Arguments are: "{args}"'.format(
args='" "'.join(self.args) args='" "'.join(self.args)
)) ))
self.runner = Runner(config=self.config,
cmds=self.config.post_run_commands)
def _rm_dockerenv(self): def _rm_dockerenv(self):
"""Delete '/.dockerenv' and '/.dockerinit' files""" """Delete '/.dockerenv' and '/.dockerinit' files"""
@ -88,6 +91,11 @@ class Command(object):
self.log.debug('Launching reloader process') self.log.debug('Launching reloader process')
self.config.reload.run_in_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): def run(self):
if not self.is_handled: if not self.is_handled:
self._exec() self._exec()
@ -107,4 +115,5 @@ class Command(object):
[p for p in subcom if fnmatch(self.args[0], p)]: [p for p in subcom if fnmatch(self.args[0], p)]:
self.args.insert(0, self.command) self.args.insert(0, self.command)
self._run_reloader() self._run_reloader()
self._run_post_commands()
self._exec() self._exec()

View File

@ -203,6 +203,11 @@ class Config(ConfigMeta):
"""Return list of postconf commands""" """Return list of postconf commands"""
return self._return_item_lst('post_conf_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 @property
def reload(self): def reload(self):
"""Return Reloader object if reload is set""" """Return Reloader object if reload is set"""
@ -239,3 +244,20 @@ class Config(ConfigMeta):
if 'ENTRYPOINT_QUIET' in os.environ: if 'ENTRYPOINT_QUIET' in os.environ:
return True return True
return bool(self._config.get('quiet', False)) 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))

View File

@ -8,11 +8,8 @@ from __future__ import unicode_literals
import json import json
import os import os
from subprocess import PIPE
from subprocess import Popen
from sys import argv from sys import argv
from sys import exit from sys import exit
from sys import stdout
import yaml import yaml
from jinja2 import Environment from jinja2 import Environment
@ -22,6 +19,7 @@ from .config import Config
from .constants import ENTRYPOINT_FILE from .constants import ENTRYPOINT_FILE
from .docker_links import DockerLinks from .docker_links import DockerLinks
from .logs import Logs from .logs import Logs
from .runner import Runner
__all__ = ['Entrypoint', 'main'] __all__ = ['Entrypoint', 'main']
@ -49,6 +47,7 @@ class Entrypoint(object):
if self.config.quiet: if self.config.quiet:
Logs.set_critical() Logs.set_critical()
self.args = args self.args = args
self.runner = Runner(config=self.config)
@property @property
def is_handled(self): def is_handled(self):
@ -58,21 +57,21 @@ class Entrypoint(object):
@property @property
def is_disabled(self): def is_disabled(self):
"""Return if service is disabled using environment""" """Return if service is disabled using environment"""
return 'ENTRYPOINT_DISABLE_SERVICE' in os.environ return self.config.is_disabled
@property @property
def should_config(self): def should_config(self):
"""Check environment to tell if config should apply anyway""" """Check environment to tell if config should apply anyway"""
return 'ENTRYPOINT_FORCE' in os.environ return self.config.should_config
@property @property
def raw_output(self): def raw_output(self):
"""Check if command output should be displayed using logging or not""" """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): def exit_if_disabled(self):
"""Exist 0 if service is disabled""" """Exist 0 if service is disabled"""
if not self.is_disabled: if not self.config.is_disabled:
return return
self.log.warning("Service is disabled by 'ENTRYPOINT_DISABLE_SERVICE' " self.log.warning("Service is disabled by 'ENTRYPOINT_DISABLE_SERVICE' "
@ -94,41 +93,17 @@ class Entrypoint(object):
yaml=yaml, yaml=yaml,
containers=DockerLinks().to_containers())) 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): def run_pre_conf_cmds(self):
for cmd in self.config.pre_conf_commands: 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: 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): def run_post_conf_cmds(self):
for cmd in self.config.post_conf_commands: 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: 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): def launch(self):
self.config.command.run() self.config.command.run()

52
pyentrypoint/runner.py Normal file
View File

@ -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()

View File

@ -1,23 +1 @@
argparse==1.4.0 docker-compose==1.7.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

View File

@ -5,7 +5,7 @@ from setuptools import setup
# Thanks Sam and Max # Thanks Sam and Max
__version__ = '0.4.7' __version__ = '0.5.0'
if __name__ == '__main__': if __name__ == '__main__':
setup( setup(

5
tests/configs/runner.yml Normal file
View File

@ -0,0 +1,5 @@
post_run_commands:
- sleep 2
- echo 'OK' > /tmp/runner_test
command: sleep

25
tests/runner_test.py Normal file
View File

@ -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