pull/1/head
Christophe Mehay 9 years ago
parent fc23fbf874
commit 4a01020efd

@ -0,0 +1,18 @@
- repo: git://github.com/pre-commit/pre-commit-hooks
sha: 'v0.4.2'
hooks:
- id: check-added-large-files
- id: check-docstring-first
- id: check-merge-conflict
- id: check-yaml
- id: end-of-file-fixer
- id: flake8
- id: name-tests-test
- id: autopep8-wrapper
- id: requirements-txt-fixer
- id: trailing-whitespace
- repo: git://github.com/asottile/reorder_python_imports
sha: 3d86483455ab5bd06cc1069fdd5ac57be5463f10
hooks:
- id: reorder-python-imports
language_version: 'python2.7'

@ -2,11 +2,16 @@
FROM python:2
RUN pip install pytest twiggy six pyyaml
RUN pip install pytest twiggy six pyyaml jinja2
ADD python-entrypoint /opt/python-entrypoint/
ADD tests/entrypoint-config.yml /opt/python-entrypoint/
ENV PYTHONPATH /opt/pyentrypoint/
WORKDIR /opt/python-entrypoint/
ADD pyentrypoint /opt/pyentrypoint/
ADD tests /opt/pyentrypoint/tests
ADD tests/entrypoint-config.yml /opt/pyentrypoint/
ADD tests/test_template /tmp/tests
WORKDIR /opt/pyentrypoint/
CMD ["py.test", "-s", "."]

@ -2,11 +2,16 @@
FROM python:3
RUN pip3 install pytest twiggy six pyyaml
RUN pip3 install pytest twiggy six pyyaml jinja2
ADD python-entrypoint /opt/python-entrypoint/
ADD tests/entrypoint-config.yml /opt/python-entrypoint/
ENV PYTHONPATH /opt/pyentrypoint/
WORKDIR /opt/python-entrypoint/
ADD pyentrypoint /opt/pyentrypoint/
ADD tests /opt/pyentrypoint/tests
ADD tests/entrypoint-config.yml /opt/pyentrypoint/
ADD tests/test_template /tmp/tests
WORKDIR /opt/pyentrypoint/
CMD ["py.test", "-s", "."]

@ -43,14 +43,24 @@ links:
# env can be list, dict or string
env:
FOO: bar
# Single doesn't allow multiple links for this id
# false by default
single: true
# Set to false to get optional link
# true by default
required: true
# Commands to run before applying configuration
pre_conf_commands:
- echo something useless
# commands to run after applying configuration
post_conf_commads:
post_conf_commands:
- echo something even useless
# Cleanup environment from variables created by linked containers
# before running command (True by default)
clean_env: True
# Enable debug to debug
debug: true

@ -0,0 +1,49 @@
"Command object"
import fnmatch
import os
class Command(object):
"""This object handle command in dockerfile"""
def __init__(self, command, config, args):
self.config = config
self.args = args
self.command = command
self.env = os.environ
def _clean_links_env(self):
# TODO: that Looks too much complicated
all_link_names = []
def is_link_env(env, links):
for link in links:
patterns = [
'{}_NAME'.format(link),
'{}_PORT_*'.format(link),
'{}_ENV_*'.format(link),
]
for patt in patterns:
if fnmatch.fnmatch(link, patt):
return True
return False
for link in self.config.links.all:
all_link_names.extend(link.names)
self.env = {env: val for env, val in os.environ.items()
if not is_link_env(env, all_link_names)}
def run(self):
if os.getuid() is 0:
os.setuid(self.config.user)
os.setgid(self.config.group)
if self.config.clean_env:
self._clean_links_env()
for item in self.config.secret_env:
if item in os.environ:
del(self.env[item])
if not self.args or \
fnmatch.filter(self.config.subcommands, self.args[0]):
args = self.args if self.args else [self.command]
os.execvpe(self.command, args, os.environ)
os.execvpe(args[0], args, os.environ)

@ -1,17 +1,18 @@
"""
Configuration
"""
import fnmatch
import os
from grp import getgrnam
from io import open
from pwd import getpwnam
from six import string_types, viewitems
from command import Command
from docker_links import DockerLinks
from yaml import Loader, load
from six import string_types
from six import viewitems
from yaml import load
from yaml import Loader
class Link(object):
@ -30,30 +31,25 @@ class Link(object):
)
self.names = tuple(names)
def _filter_name(self, name):
"return true if name match"
return bool(fnmatch.filter(self.names, name))
class Filter(object):
"""Filtering links"""
@staticmethod
def name(link, name):
return bool(fnmatch.filter(link.names, name))
def _filter_port(self, port):
"return true if port match"
return int(port) == self.port
@staticmethod
def port(link, port):
return int(port) == link.port
def _filter_protocol(self, protocol):
"return true if protocol match"
return protocol == self.protocol
@staticmethod
def protocol(link, protocol):
return protocol == link.protocol
@staticmethod
def env(link, env):
def _filter_env(self, env):
"return true if env match"
if isinstance(env, dict):
return viewitems(env) <= viewitems(link.env)
return viewitems(env) <= viewitems(self.env)
if isinstance(env, list):
return bool([key for key in env if key in link.env])
return str(env) in link.env
return bool([key for key in env if key in self.env])
return str(env) in self.env
class Links(object):
@ -62,6 +58,8 @@ class Links(object):
_links = []
_conf = None
_options = {'single': False,
'required': True}
def __init__(self, links=None, config=None):
if not links or len(links.links()) is 0:
@ -79,15 +77,28 @@ class Links(object):
def _get_link(self, name):
config = self._conf[name]
print(config)
links = list(self._links)
links = self._links
print('len all:', len(links))
for key, val in config.items():
links = [link for link in links if getattr(Filter, key)(link, val)]
if key in self._options:
self._options[key] = val
continue
links = [link for link in links
if getattr(link, "_filter_{}".format(key))(val)]
print('len:', len(links))
if self._options['required'] and len(links) is 0:
raise Exception("No links was found for {name}".format(name=name))
if self._options['single'] and len(links) > 1:
raise Exception("Only one link should be provided for {name}"
.format(name=name))
if self._options['single']:
return links[0]
return tuple(links)
@classmethod
def _add_name(cls, name):
"Add method attribute name"
setattr(cls, name, property(lambda self: self._get_link(name)))
@property
@ -105,6 +116,8 @@ class Config(object):
_config_file = 'entrypoint-config.yml'
_config = {}
_links = None
_args = []
def _return_item_lst(self, item):
"""Return item as a list"""
@ -114,7 +127,7 @@ class Config(object):
return self._config[item]
return []
def __init__(self):
def __init__(self, args=[]):
if not os.path.isfile(self._config_file):
return
try:
@ -123,6 +136,7 @@ class Config(object):
except Exception as err:
# TODO: logger
print(err)
self._args = args
@property
def as_config(self):
@ -132,10 +146,11 @@ class Config(object):
@property
def command(self):
"Main command to run."
if 'command' in self._config:
return self._config['command']
elif 'cmd' in self._config:
return self._config['cmd']
cmd = self._args[0] if self._args else ''
for key in ['command', 'cmd']:
if key in self._config:
cmd = self._config[key]
return Command(cmd, self, self._args)
@property
def subcommands(self):
@ -172,26 +187,36 @@ class Config(object):
@property
def links(self):
"""Links configs."""
"""Links configs singleton."""
links = DockerLinks()
if self._links:
return self._links
if 'links' not in self._config:
return Links(links=links)
links = Links(links=links, config=self._config['links'])
self._links = Links(links=links)
return self._links
self._links = Links(links=links, config=self._config['links'])
for name in self._config['links']:
links._add_name(name)
return links
self._links._add_name(name)
return self._links
@property
def pre_conf_commands(self):
"""Return list of preconf commands"""
if 'pre_conf_commands' in self._config:
return self._return_item_lst(self._config['pre_conf_command'])
@property
def pre_conf_command(self):
"""Return Exec object of preconf command"""
if 'pre_conf_command' in self._config:
return self._config['pre_conf_command']
def post_conf_commands(self):
"""Return list of postconf commands"""
if 'post_conf_commands' in self._config:
return self._return_item_lst(self._config['post_conf_command'])
@property
def post_conf_command(self):
"""Return Exec object of preconf command"""
if 'post_conf_command' in self._config:
return self._config['post_conf_command']
def clean_env(self):
"""Clean env from linked containers before running command"""
if 'clean_env' in self._config:
return bool(self._config['clean_env'])
return True
@property
def debug(self):

@ -1,10 +1,8 @@
#!/usr/bin/env python
"""
DockerLinks a kiss class which help to get links info in a docker
container.
"""
import json
import os
import re

@ -0,0 +1,71 @@
#!/usr/bin/env python
"""
Smart docker-entrypoint
"""
from subprocess import PIPE
from subprocess import Popen
from sys import argv
from command import Command
from config import Config
from jinja2 import Environment
from jinja2 import FileSystemLoader
from twiggy import levels
from twiggy import log
from twiggy import quickSetup
class Entrypoint(object):
"""Entrypoint class."""
def _set_logguer(self):
quickSetup(min_level=levels.INFO)
self.log = log.name('entrypoint')
def __init__(self, args=[]):
self._set_logguer()
try:
self.config = Config()
except Exception as err:
self.log.error(err)
if self.config.debug:
quickSetup(min_level=levels.DEBUG)
self.args = args
def apply_conf(self):
env = Environment(loader=FileSystemLoader('/'))
for template in self.config.config_files:
temp = env.get_template(template)
with open(template, mode='w') as f:
self.log.info('Applying conf to {}'.format(template))
f.write(temp.render(config=self.config,
links=self.config.links))
def run_conf_cmd(self, cmd):
self.log.info('run command: {}'.format(cmd))
proc = Popen(cmd, shell=True, stdout=PIPE, stderr=PIPE)
out, err = proc.communicate()
self.log.info(out)
self.log.warning(err)
if proc.returncode:
raise Exception('Command exit code: {}'.format(proc.returncode))
def launch(self):
self.args.pop(0)
command = Command(self.config, self.args)
command.run()
if __name__ == '__main__':
entry = Entrypoint(argv)
try:
for cmd in entry.config.pre_conf_commands:
entry.run_conf_cmd(cmd)
entry.apply_conf()
for cmd in entry.config.post_conf_commands:
entry.run_conf_cmd(cmd)
entry.launch()
except Exception as e:
print(e)

@ -0,0 +1,7 @@
"""
Custom exceptions
"""
class BadLink(Exception):
pass

@ -1,27 +0,0 @@
#!/usr/bin/env python
"""
Smart docker-entrypoint
"""
from twiggy import levels, log, quickSetup
from config import Config
class Entrypoint(object):
"""Entrypoint class."""
def _set_logguer(self):
quickSetup(min_level=levels.INFO)
self.log = log.name('entrypoint')
def __init__(self):
self._set_logguer()
try:
self.config = Config()
except Exception as err:
self.log.error(err)
if self.config.debug:
quickSetup(min_level=levels.DEBUG)

@ -0,0 +1,25 @@
command: echo
subcommands:
- OK
user: 1000
group: 1000
config_files:
- /tmp/tests
secret_env:
- SSHKEY
links:
test1:
name: test1
test2_800:
port: 800
protocol: udp
single: true
test2:
env:
FOO: bar
required: true

@ -1,5 +1,4 @@
# Tests using pytest
from docker_links import DockerLinks
from entrypoint import Entrypoint
@ -60,20 +59,23 @@ def test_ports():
assert item["ports"]["8001"]['protocol'] == 'tcp'
def test_entrypoint():
def test_entrypoint_links():
entry = Entrypoint()
links = entry.config.links
assert len(links.all) == 4
assert len(links.test1) == 2
assert links.test2_800.port == 800
def test_templates():
entry = Entrypoint()
conf = entry.config
entry.apply_conf()
print(vars(entry.config))
print('all')
for link in entry.config.links.all:
print(vars(link))
print('test1')
for link in entry.config.links.test1:
print(vars(link))
print('test2_800')
for link in entry.config.links.test2_800:
print(vars(link))
print('test1')
for link in entry.config.links.test2:
print(vars(link))
with open(conf.config_files[0], mode='r') as r:
print(r.read())

@ -0,0 +1,3 @@
{% for link in links.all %}
{{link.uri}}
{% endfor %}
Loading…
Cancel
Save