├── .coveralls.yml ├── tow ├── __init__.py ├── commands │ ├── __init__.py │ ├── command.py │ ├── create.py │ ├── process.py │ ├── run.py │ ├── build.py │ └── utils.py ├── version.py ├── templates │ ├── Dockerfile.tmpl │ ├── default.py.tmpl │ ├── tow.sh.tmpl │ ├── mapping.py.tmpl │ ├── mapping.sh.tmpl │ └── __init__.py ├── attrs.py ├── modules.py ├── main.py └── dockerfile.py ├── setup.cfg ├── docs ├── images │ ├── docker-hub.png │ └── tow-process.png ├── introduction.md └── getting-started │ ├── step-1-first-step.md │ └── step-2-deal-with-dynamic-configuration.md ├── .travis.yml ├── Vagrantfile ├── .gitignore ├── tests ├── utils_tests.py └── dockerfile_tests.py ├── setup.py ├── README.md └── LICENSE /.coveralls.yml: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tow/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tow/commands/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | description-file = README.md 3 | -------------------------------------------------------------------------------- /docs/images/docker-hub.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/docker-tow/tow/HEAD/docs/images/docker-hub.png -------------------------------------------------------------------------------- /tow/version.py: -------------------------------------------------------------------------------- 1 | """ 2 | This module contains tow version 3 | """ 4 | 5 | version = '1.0.0-alpha-13' 6 | -------------------------------------------------------------------------------- /docs/images/tow-process.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/docker-tow/tow/HEAD/docs/images/tow-process.png -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - "2.7" 4 | script: python setup.py test 5 | after_success: 6 | - coveralls 7 | -------------------------------------------------------------------------------- /tow/templates/Dockerfile.tmpl: -------------------------------------------------------------------------------- 1 | # Template Dockerfile was generated by tow 2 | FROM scratch 3 | MAINTAINER , 4 | CMD ["/opt/run.sh"] 5 | -------------------------------------------------------------------------------- /tow/templates/default.py.tmpl: -------------------------------------------------------------------------------- 1 | # 2 | # Tow project: {{project_name}} 3 | # Attributes: default 4 | # 5 | # Copyright {{current_year}}, YOUR_COMPANY_NAME 6 | # 7 | # All rights reserved - Do Not Redistribute 8 | # 9 | -------------------------------------------------------------------------------- /tow/commands/command.py: -------------------------------------------------------------------------------- 1 | """ 2 | TODO: add comments 3 | """ 4 | 5 | 6 | class Command(object): 7 | 8 | def add_parser(self, subparsers): 9 | pass 10 | 11 | def command(namespace, args): 12 | pass 13 | -------------------------------------------------------------------------------- /tow/templates/tow.sh.tmpl: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # This is tow run script which copy files from tow volume 3 | 4 | if [ -f "{{volume_name}}/mapping.sh" ]; then 5 | sh {{volume_name}}/mapping.sh 6 | fi 7 | 8 | PATH=$PATH:`pwd` 9 | 10 | {% for cmd in command -%}"{{ cmd }}" {% endfor %} 11 | -------------------------------------------------------------------------------- /Vagrantfile: -------------------------------------------------------------------------------- 1 | # -*- mode: python-*- 2 | 3 | Vagrant.configure('2') do |config| 4 | config.vm.provider "virtualbox" do |v, override| 5 | override.vm.box = 'Ubuntu 14.10' 6 | override.vm.box_url = "https://github.com/jose-lpa/packer-ubuntu_14.04/releases/download/v2.0/ubuntu-14.04.box" 7 | 8 | v.memory = 1024 9 | v.cpus = 2 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /tow/templates/mapping.py.tmpl: -------------------------------------------------------------------------------- 1 | # 2 | # Tow project: {{project_name}} 3 | # Default mapping configuration 4 | # 5 | # Copyright {{current_year}}, YOUR_COMPANY_NAME 6 | # 7 | # All rights reserved - Do Not Redistribute 8 | # 9 | 10 | mapping = { 11 | "templates": [ 12 | ("/path/to/template", "/path/in/container") 13 | ], 14 | "files": [ 15 | ("/path/to/file", "/path/in/container") 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /tow/attrs.py: -------------------------------------------------------------------------------- 1 | """ 2 | TODO: add comments 3 | """ 4 | from modules import load_module 5 | 6 | 7 | def process_attrs(env, attributes_path, name="default"): 8 | """ This method load attrs files and process attributes """ 9 | mod = load_module(env, attributes_path, name) 10 | if mod: 11 | attr_names = [var for var in dir(mod) if not var.startswith("__")] 12 | return {attr_name: getattr(mod, attr_name) for attr_name in attr_names} 13 | return {} 14 | -------------------------------------------------------------------------------- /tow/modules.py: -------------------------------------------------------------------------------- 1 | """ 2 | TODO: add comments 3 | """ 4 | import imp 5 | import sys 6 | import os 7 | 8 | 9 | def load_module(env, module_path, name): 10 | """ This method load module file """ 11 | module_file = os.path.join(module_path, "%s.py" % name) 12 | mod = None 13 | if os.path.exists(module_file): 14 | mod = imp.new_module(name) 15 | mod.__dict__.update({"env": env}) 16 | sys.modules[name] = mod 17 | mod = imp.load_source(name, module_file) 18 | return mod 19 | -------------------------------------------------------------------------------- /tow/templates/mapping.sh.tmpl: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # This is tow run script which copy files from tow volume 3 | {% for file_mapping in mapping %} 4 | if [ -f "{{volume_name}}/{{file_mapping[0]}}" ]; then 5 | dst_dir=`dirname {{file_mapping[1]}}` 6 | if [ ! -d "$dst_dir" ]; then 7 | mkdir -p "$dst_dir" 8 | fi 9 | cp "{{volume_name}}/{{file_mapping[0]}}" "{{file_mapping[1]}}" 10 | {% if file_mapping|length > 2 %} 11 | chmod {{file_mapping[2]}} {{file_mapping[1]}} 12 | {% endif %} 13 | fi 14 | {% endfor %} 15 | -------------------------------------------------------------------------------- /tow/templates/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | This module contains tow process template method and templates itself. 3 | 4 | """ 5 | 6 | import jinja2 7 | import os 8 | 9 | 10 | def process(template_path, template_name, result_file_path, context): 11 | """ 12 | Process template 13 | """ 14 | loader = jinja2.FileSystemLoader(template_path) 15 | jinja_env = jinja2.Environment(loader=loader) 16 | template = jinja_env.get_template(template_name) 17 | result = template.render(context) 18 | 19 | with open(result_file_path, "w+") as f: 20 | f.write(result) 21 | 22 | 23 | def process_template(template_name, result_file_path, context): 24 | """ 25 | Process template form templates folder 26 | """ 27 | template_path = os.path.dirname(os.path.abspath(__file__)) 28 | process(template_path, template_name, result_file_path, context) 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | 5 | # C extensions 6 | *.so 7 | 8 | # Distribution / packaging 9 | .Python 10 | env/ 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | lib/ 17 | lib64/ 18 | parts/ 19 | sdist/ 20 | var/ 21 | *.egg-info/ 22 | .installed.cfg 23 | *.egg 24 | 25 | # PyInstaller 26 | # Usually these files are written by a python script from a template 27 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 28 | *.manifest 29 | *.spec 30 | 31 | # Installer logs 32 | pip-log.txt 33 | pip-delete-this-directory.txt 34 | 35 | # Unit test / coverage reports 36 | htmlcov/ 37 | .tox/ 38 | .coverage 39 | .cache 40 | nosetests.xml 41 | coverage.xml 42 | 43 | # Translations 44 | *.mo 45 | *.pot 46 | 47 | # Django stuff: 48 | *.log 49 | 50 | # Sphinx documentation 51 | docs/_build/ 52 | 53 | # PyBuilder 54 | target/ 55 | 56 | .vagrant 57 | 58 | **/*.pyc 59 | *.pyc 60 | *.swp 61 | *.swn 62 | *.swo 63 | *.swm 64 | -------------------------------------------------------------------------------- /tow/commands/create.py: -------------------------------------------------------------------------------- 1 | """ 2 | TODO: add comments 3 | """ 4 | from command import Command 5 | from tow import templates 6 | import os 7 | from datetime import date 8 | 9 | 10 | class CreateCommand(Command): 11 | 12 | def add_parser(self, subparsers): 13 | super(CreateCommand, self).add_parser(subparsers) 14 | parser = subparsers.add_parser("create", 15 | help="Create tow project in current directory") 16 | parser.add_argument("project_name", type=str, 17 | help="name of tow project") 18 | 19 | def command(self, namespace, args): 20 | project_name = namespace.project_name 21 | for dir_name in ["attributes", "files", "templates"]: 22 | os.makedirs(os.path.join(project_name, dir_name)) 23 | for file_name in ["Dockerfile", "mapping.py", "attributes/default.py"]: 24 | templates.process_template("%s.tmpl" % os.path.basename(file_name), 25 | os.path.join(project_name, file_name), 26 | {"current_year": date.today().year, 27 | "project_name": project_name}) 28 | -------------------------------------------------------------------------------- /tests/utils_tests.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from tow.commands.utils import get_env_args 3 | import os 4 | 5 | 6 | class UtilsTest(unittest.TestCase): 7 | 8 | def test_get_env_args(self): 9 | envs = get_env_args(["-e", "test=1"]) 10 | self.assertEqual(envs, {"test": "1"}) 11 | 12 | def test_get_env_args_without_val(self): 13 | os.environ["test"] = "1" 14 | envs = get_env_args(["-e", "test"]) 15 | self.assertEqual(envs, {"test": "1"}) 16 | 17 | def test_get_env_args_by_envs(self): 18 | envs = get_env_args(["--env", "test=1"]) 19 | self.assertEqual(envs, {"test": "1"}) 20 | 21 | def test_get_env_args_by_envs_without_val(self): 22 | os.environ["test"] = "1" 23 | envs = get_env_args(["--env", "test"]) 24 | self.assertEqual(envs, {"test": "1"}) 25 | 26 | def test_get_env_args_by_envs_array(self): 27 | os.environ["test"] = "1" 28 | envs = get_env_args(["--env", "test", "test2=2"]) 29 | self.assertEqual(envs, {"test": "1", "test2": "2"}) 30 | 31 | def test_get_env_args_by_envs_array_and_start_next_command(self): 32 | os.environ["test"] = "1" 33 | envs = get_env_args(["--env", "test", "test2=2", "-v", "/t:/t"]) 34 | self.assertEqual(envs, {"test": "1", "test2": "2"}) 35 | -------------------------------------------------------------------------------- /tow/commands/process.py: -------------------------------------------------------------------------------- 1 | """ 2 | TODO: add comments 3 | """ 4 | from command import Command 5 | from utils import init_tow 6 | from utils import get_env_args 7 | from utils import get_linked_container_variables 8 | 9 | 10 | class ProcessCommand(Command): 11 | 12 | def add_parser(self, subparsers): 13 | super(ProcessCommand, self).add_parser(subparsers) 14 | parser = subparsers.add_parser("process", 15 | help="process configuration like in tow run but don't run docker") 16 | parser.add_argument("--tow-attrs", type=str, default="default", 17 | help="specify name of attribute file in attributes folder. Default value: default. Example --tow-attrs prod") 18 | parser.add_argument("--tow-mapping", type=str, default="mapping", 19 | help="specify name of mapping variable in mapping.py. Default value:mapping. Example --tow-mapping prod") 20 | 21 | def command(self, namespace, args): 22 | linked_envs = get_linked_container_variables(args) 23 | env_args = get_env_args(args) 24 | env_args.update(linked_envs) 25 | (file_mapping, dockerfile, envs, attrs, workingdir) = init_tow(env_args=env_args, 26 | attributes_name=namespace.tow_attrs, 27 | mapping_name=namespace.tow_mapping) 28 | -------------------------------------------------------------------------------- /docs/introduction.md: -------------------------------------------------------------------------------- 1 | Getting started with Tow 2 | ========================= 3 | 4 | ###### Learn how to configure Docker containers with Tow by following practical examples. 5 | 6 | ## Introduction 7 | 8 | Tow is a configuration management for Docker containers. Tow helps you describe your configuration files and file mapping with code. Because your configuration is managed with code, it can be automated and reproduced with ease. 9 | 10 | Taking your first steps with Tow can be . There are new concepts to learn and best practices to understand. After completing each chapter of this guide you will have a working example of a Tow setup that you understand. Concepts and best practices are explained by example so you'll know how to customize the code for your own needs. 11 | 12 | We work hard to improve Tow and this getting started guide, but you might find some issues with the examples in this book. If you get stuck following an example or have any suggestions of things we should change please email . 13 | 14 | ### Table of Content 15 | 16 | - **[1. First step](getting-started/step-1-first-step.md)** 17 | 18 | The first step introduces you tow installation and creation of a basic project for Nginx deployment inside the container. Also this step will describe project structure and main Tow commands. 19 | 20 | - **[2. Deal with dynamic configuration](getting-started/step-2-deal-with-dynamic-configuration.md)** 21 | 22 | This step describes files and templates processing, mapping between local and container filesystem. 23 | 24 | - **[3. Understanding Build and Run Commands](#)** 25 | 26 | The last step builds our example Nginx container and runs it with different configurations. 27 | 28 | ### [Next: 1. First step](getting-started/step-1-first-step.md) -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from __future__ import with_statement 4 | from setuptools import setup, find_packages 5 | from tow.version import version 6 | 7 | long_description = """Tow provides a workflow for building docker images 8 | with dynamics configuration files using templates. 9 | The main concept is processing all configuration 10 | templates outside of container and then build 11 | image using pre-processed files.""" 12 | 13 | 14 | setup( 15 | name='tow', 16 | version=version, 17 | description='Tow is tool for automatization docker configuration managment workflow', 18 | long_description=long_description, 19 | author='Aleksei Kornev, Nikolay Yurin', 20 | author_email='aleksei.kornev@gmail.com, ', 21 | url='https://github.com/alekseiko/tow', 22 | packages=find_packages(), 23 | test_suite='nose.collector', 24 | tests_require=['nose'], 25 | install_requires=['jinja2'], 26 | package_data={ 27 | '': ['*.tmpl'], 28 | }, 29 | entry_points={ 30 | 'console_scripts': [ 31 | 'tow = tow.main:main', 32 | ] 33 | }, 34 | classifiers=[ 35 | 'Environment :: Console', 36 | 'Intended Audience :: Developers', 37 | 'Intended Audience :: System Administrators', 38 | 'Operating System :: Unix', 39 | 'Operating System :: POSIX', 40 | 'Programming Language :: Python', 41 | 'Programming Language :: Python :: 2.7', 42 | 'Topic :: Software Development', 43 | 'Topic :: Software Development :: Build Tools', 44 | 'Topic :: System :: Clustering', 45 | 'Topic :: System :: Software Distribution', 46 | 'Topic :: System :: Systems Administration', 47 | ], 48 | 49 | ) 50 | -------------------------------------------------------------------------------- /tow/main.py: -------------------------------------------------------------------------------- 1 | """ 2 | This module contains tow `main` method. 3 | 4 | `main` is executed as the command line ``tow`` program and takes care of 5 | parsing options and commands, loading the user settings file 6 | and executing the commands given. 7 | 8 | The other callables defined in this module are internal only. 9 | """ 10 | 11 | import sys 12 | from version import version 13 | from argparse import ArgumentParser 14 | from commands.create import CreateCommand 15 | from commands.build import BuildCommand 16 | from commands.run import RunCommand 17 | from commands.process import ProcessCommand 18 | 19 | # don't create pyc files 20 | sys.dont_write_bytecode = True 21 | 22 | 23 | def main(): 24 | """ 25 | Main command-line execution loop 26 | """ 27 | tow_parser = ArgumentParser(description="tow is configuration managment tool for docker containers", version=version) 28 | tow_subparsers = tow_parser.add_subparsers(help="tow commands", dest="command") 29 | 30 | commands = {} 31 | 32 | create_command = CreateCommand() 33 | create_command.add_parser(tow_subparsers) 34 | commands["create"] = create_command 35 | 36 | build_command = BuildCommand() 37 | build_command.add_parser(tow_subparsers) 38 | commands["build"] = build_command 39 | 40 | run_command = RunCommand() 41 | run_command.add_parser(tow_subparsers) 42 | commands["run"] = run_command 43 | 44 | process_command = ProcessCommand() 45 | process_command.add_parser(tow_subparsers) 46 | commands["process"] = process_command 47 | 48 | if len(sys.argv) > 1: 49 | (namespace, args) = tow_parser.parse_known_args() 50 | 51 | if namespace.command not in commands: 52 | tow_parser.print_help() 53 | else: 54 | commands[namespace.command].command(namespace, args) 55 | else: 56 | tow_parser.print_help() 57 | sys.exit(0) 58 | -------------------------------------------------------------------------------- /tow/commands/run.py: -------------------------------------------------------------------------------- 1 | """ 2 | TODO: add comments 3 | """ 4 | from command import Command 5 | from utils import init_tow 6 | from utils import get_env_args 7 | from utils import get_linked_container_variables 8 | import subprocess 9 | import os 10 | from utils import TOW_VOLUME 11 | 12 | 13 | class RunCommand(Command): 14 | 15 | def add_parser(self, subparsers): 16 | super(RunCommand, self).add_parser(subparsers) 17 | parser = subparsers.add_parser("run", 18 | help="run docker container if tow build was without --tow-run option than call docker run else process attributes and tempaltes mount /tow volume and run docker run with DOCKER-OPTIONS") 19 | parser.add_argument("--tow-attrs", type=str, default="default", 20 | help="specify name of attribute file in attributes folder. Default value: default. Example --tow-attrs prod") 21 | parser.add_argument("--tow-mapping", type=str, default="mapping", 22 | help="specify name of mapping variable in mapping.py. Default value:mapping. Example --tow-mapping prod") 23 | 24 | def command(self, namespace, args): 25 | linked_envs = get_linked_container_variables(args) 26 | env_args = get_env_args(args) 27 | env_args.update(linked_envs) 28 | (file_mapping, dockerfile, envs, attrs, workingdir) = init_tow(env_args=env_args, 29 | attributes_name=namespace.tow_attrs, 30 | mapping_name=namespace.tow_mapping) 31 | 32 | try: 33 | subprocess.call(["docker", "run", "-v", "%s:%s" % (workingdir, TOW_VOLUME)] + args) 34 | except OSError as e: 35 | if e.errno == os.errno.ENOENT: 36 | print "ERORR: Please install docker and run tow again" 37 | -------------------------------------------------------------------------------- /docs/getting-started/step-1-first-step.md: -------------------------------------------------------------------------------- 1 | ### [Back: Introduction](../introduction.md) 2 | 3 | 1. First step 4 | ============= 5 | 6 | ## Installing Tow 7 | 8 | To install Tow simply run: 9 | 10 | ```console 11 | $ pip install tow 12 | ``` 13 | 14 | Confirm Tow has successfully installed. 15 | 16 | ```console 17 | $ tow 18 | tow is configuration management tool for docker containers 19 | ... 20 | DOCKER-OPTIONS - options for docker build or run command 21 | ``` 22 | 23 | ## Create Tow project 24 | 25 | To get Nginx up and running we need: 26 | 27 | - Install Nginx 28 | - Generate site config 29 | - Generate example webpage 30 | 31 | Let's create Tow project and start implementing. 32 | 33 | ```console 34 | $ tow create tow-nginx-example 35 | ``` 36 | 37 | If we look inside the Tow project directory we can see the following: 38 | 39 | ```console 40 | $ cd tow-nginx-example 41 | $ ls 42 | Dockerfile attributes/ files/ mapping.py templates/ 43 | 44 | ``` 45 | 46 | So now we need to write our Dockerfile to install and configure Nginx server. How do we do that? First of all we should peek a base Docker image. Thanks to [Docker Hub](https://registry.hub.docker.com) it's really easy. 47 | 48 | ![Docker Hub](../images/docker-hub.png) 49 | 50 | Here is a lot of prebuild, tested Docker images you. Let's use [debian](https://registry.hub.docker.com/_/debian/), it's a stable lightweight image (only ~90 mb) which is definitely recommended for using like a base image for your Dockerfiles. Open `Dockerfile` in your favorite text editor and make it looks like this: 51 | 52 | ```Dockerfile 53 | FROM debian:jessie 54 | 55 | RUN apt-get update && \ 56 | apt-get install -y nginx 57 | 58 | RUN rm -rf /var/lib/apt/lists/* && \ 59 | chown -R www-data:www-data /var/lib/nginx 60 | 61 | VOLUME /var/www/html 62 | WORKDIR /etc/nginx 63 | EXPOSE 80 64 | 65 | CMD ["nginx", "-g", "daemon off;"] 66 | ``` 67 | 68 | ## First run 69 | 70 | Now we have a Dockerfile which just installs and starts Nginx server. 71 | Okay. Let's see what we've got so far works! 72 | 73 | ```console 74 | $ tow build -t tow-nginx-example 75 | ... 76 | Successfully built 7dcf627e2037 77 | $ tow run -d -p 8080:80 tow-nginx-example 78 | $ curl localhost:8080 79 | curl: (7) Failed to connect to localhost port 8080: Connection refused 80 | ``` 81 | 82 | See, it still doesn't work, because we still have to add site configuration and web page into our tow-nginx-example image. Commit you file into git and move to the next step. 83 | 84 | ```console 85 | $ git add -A 86 | $ git commit -m 'Initial commit' 87 | ``` 88 | 89 | ### [Next: 2. Deal with dynamic configuration](step-2-deal-with-dynamic-configuration.md) -------------------------------------------------------------------------------- /tow/commands/build.py: -------------------------------------------------------------------------------- 1 | """ 2 | TODO: add comments 3 | """ 4 | from command import Command 5 | from utils import init_tow 6 | from tow import templates 7 | import subprocess 8 | from utils import TOW_VOLUME 9 | import os 10 | 11 | 12 | class BuildCommand(Command): 13 | 14 | def add_parser(self, subparsers): 15 | super(BuildCommand, self).add_parser(subparsers) 16 | parser = subparsers.add_parser("build", 17 | help="process attributes and tamplates path Dockerfile according mapping and run docker build with DOCKER-OPTIONS") 18 | parser.add_argument("--tow-run", action="store_true", 19 | help="patch docker file in order to use configuration in run phase") 20 | parser.add_argument("--tow-attrs", type=str, default="default", 21 | help="specify name of attribute file in attributes folder. Default value: default. Example --tow-attrs prod") 22 | parser.add_argument("--tow-mapping", type=str, default="mapping", 23 | help="specify name of mapping variable in mapping.py. Default value:mapping. Example --tow-mapping prod") 24 | 25 | def command(self, namespace, args): 26 | """ 27 | This is build command. Prepare workingdir(.tow) and run docker build 28 | """ 29 | (file_mapping, dockerfile, envs, attrs, workingdir) = init_tow(attributes_name=namespace.tow_attrs, 30 | mapping_name=namespace.tow_mapping) 31 | # Check if you would like to patch Dockerfile in order to use 32 | # reconfiguration on run phase 33 | if namespace.tow_run: 34 | command = [] 35 | (entrypoint, cmd) = dockerfile.find_entrypoint_or_cmd() 36 | if cmd: 37 | command += cmd 38 | if entrypoint: 39 | command = entrypoint + command 40 | command.append("$@") 41 | templates.process_template("tow.sh.tmpl", 42 | os.path.join(workingdir, "tow.sh"), 43 | {"command": command, 44 | "mapping": file_mapping, 45 | "volume_name": TOW_VOLUME}) 46 | file_mapping.append(("tow.sh", "/tow.sh", 755)) 47 | dockerfile.replace_entrypoint_or_cmd_by_tow_cmd("sh /tow.sh") 48 | dockerfile.add_copy(file_mapping) 49 | dockerfile.save(os.path.join(workingdir, "Dockerfile")) 50 | 51 | try: 52 | subprocess.call(["docker", "build"] + args + [workingdir]) 53 | except OSError as e: 54 | if e.errno == os.errno.ENOENT: 55 | print "ERORR: Please install docker and run tow again" 56 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Tow - configuration management tool for Docker 2 | ============================================== 3 | 4 | [![Build Status](https://travis-ci.org/docker-tow/tow.svg)](https://travis-ci.org/docker-tow/tow) 5 | [![Coverage Status](https://coveralls.io/repos/docker-tow/tow/badge.svg)](https://coveralls.io/r/docker-tow/tow) 6 | [![Dependency Status](https://gemnasium.com/docker-tow/tow.svg)](https://gemnasium.com/docker-tow/tow) 7 | [![Code Climate](https://codeclimate.com/github/docker-tow/tow/badges/gpa.svg)](https://codeclimate.com/github/docker-tow/tow) 8 | 9 | [![Stories in Ready](https://badge.waffle.io/docker-tow/tow.png?label=ready&title=Ready)](https://waffle.io/docker-tow/tow) 10 | [![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/docker-tow/tow?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) 11 | 12 | 13 | ## Overview 14 | 15 | Tow provides a workflow for building docker images with dynamics configuration files using templates. The main concept is processing all configuration templates outside of container and then build image using pre-processed files. 16 | 17 | Here how it works: 18 | 19 | ![Tow process diagram](docs/images/tow-process.png) 20 | 21 | ## Installation 22 | 23 | Latest stable release is always available on PyPi. 24 | 25 | [![PyPI version](https://badge.fury.io/py/tow.svg)](http://badge.fury.io/py/tow) 26 | [![PyPi downloads](https://pypip.in/d/tow/badge.png)](https://crate.io/packages/tow/) 27 | 28 | ``` 29 | pip install tow 30 | ``` 31 | 32 | ## Getting Started 33 | 34 | Check out [Getting Started Guide](docs/introduction.md) to get more familiar with Tow. 35 | 36 | If you are looking real world example of Tow project checkout: [tow-nginx](https://github.com/docker-tow/tow-nginx) - an example Tow project set up Nginx in most simple configuration. 37 | 38 | ## Usage 39 | 40 | ``` 41 | tow [command] 42 | 43 | Commands: 44 | create - create new tow project in current directory 45 | build - process configs and build images 46 | [--run-tow] - executes tow.sh on start 47 | run [-e ][args] - process configuration files overrides and start container 48 | ``` 49 | 50 | Tow also available in docker image. Checkout [Tow docker image documentation](https://github.com/yurinnick/tow-docker) for more information. 51 | 52 | ## License 53 | 54 | Tow is licensed under the Apache License, Version 2.0. See LICENSE for full license text 55 | 56 | ``` 57 | # 58 | # Licensed under the Apache License, Version 2.0 (the "License"); 59 | # you may not use this file except in compliance with the License. 60 | # You may obtain a copy of the License at 61 | # 62 | # http://www.apache.org/licenses/LICENSE-2.0 63 | # 64 | # Unless required by applicable law or agreed to in writing, software 65 | # distributed under the License is distributed on an "AS IS" BASIS, 66 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 67 | # See the License for the specific language governing permissions and 68 | # limitations under the License. 69 | # 70 | ``` 71 | -------------------------------------------------------------------------------- /tests/dockerfile_tests.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from tow.dockerfile import Dockerfile 3 | 4 | 5 | class DockerfileTest(unittest.TestCase): 6 | 7 | def test_parse_spaced_envs(self): 8 | d = Dockerfile("Dockerfile") 9 | d._Dockerfile__dockerfile = ["ENV test 1"] 10 | envs = d.envs() 11 | self.assertEqual(envs, {"test": "1"}) 12 | 13 | def test_parse_many_envs(self): 14 | d = Dockerfile("Dockerfile") 15 | d._Dockerfile__dockerfile = ["ENV test 1", "ENV test2=2", "ENV test3 3"] 16 | envs = d.envs() 17 | self.assertEqual(envs, {"test": "1", "test2": "2", "test3": "3"}) 18 | 19 | def test_parse_multiline(self): 20 | d = Dockerfile("Dockerfile") 21 | d._Dockerfile__dockerfile = ['ENV myName="John Doe" myDog=Rex\\ The\\ Dog \\', 22 | 'myCat=fluffy'] 23 | envs = d.envs() 24 | self.assertEqual(envs, {"myName": "John Doe", 25 | "myDog": "Rex\\ The\\ Dog", "myCat": "fluffy"}) 26 | 27 | def test_add_copy(self): 28 | d = Dockerfile("Dockerfile") 29 | d._Dockerfile__dockerfile = ["FROM ubuntu"] 30 | mapping = ("/tets1", "/test2") 31 | d.add_copy([mapping]) 32 | self.assertListEqual(d._Dockerfile__dockerfile, ["FROM ubuntu", 33 | "# TOW COPY BLOCK FROM MAPPING FILE START", 34 | "COPY %s %s" % mapping, 35 | "# TOW COPY BLOCK FROM MAPPING FILE END"]) 36 | 37 | def test_add_copy_after_from(self): 38 | d = Dockerfile("Dockerfile") 39 | d._Dockerfile__dockerfile = ["FROM ubuntu", "ENTRYPOINT [/bin/sh]"] 40 | mapping = ("/tets1", "/test2") 41 | d.add_copy([mapping]) 42 | self.assertListEqual(d._Dockerfile__dockerfile, ["FROM ubuntu", 43 | "# TOW COPY BLOCK FROM MAPPING FILE START", 44 | "COPY %s %s" % mapping, 45 | "# TOW COPY BLOCK FROM MAPPING FILE END", 46 | "ENTRYPOINT [/bin/sh]"]) 47 | 48 | 49 | def test_add_copy_after_maintainer(self): 50 | d = Dockerfile("Dockerfile") 51 | d._Dockerfile__dockerfile = ["FROM ubuntu", "MAINTAINER test","ENTRYPOINT [/bin/sh]"] 52 | mapping = ("/tets1", "/test2") 53 | d.add_copy([mapping]) 54 | self.assertListEqual(d._Dockerfile__dockerfile, ["FROM ubuntu", 55 | "MAINTAINER test", 56 | "# TOW COPY BLOCK FROM MAPPING FILE START", 57 | "COPY %s %s" % mapping, 58 | "# TOW COPY BLOCK FROM MAPPING FILE END", 59 | "ENTRYPOINT [/bin/sh]"]) 60 | 61 | def test_find_entrypoint_or_cmd(self): 62 | d = Dockerfile("Dockerfile") 63 | d._Dockerfile__dockerfile = ['FROM ubuntu', 'ENTRYPOINT ["/bin/sh"]', 'CMD ["-c"]'] 64 | self.assertEqual(d.find_entrypoint_or_cmd(), (["/bin/sh"], ["-c"])) 65 | 66 | def test_find_entrypoint_or_cmd_shell_style(self): 67 | d = Dockerfile("Dockerfile") 68 | d._Dockerfile__dockerfile = ['FROM ubuntu', 'ENTRYPOINT /bin/sh', 'CMD ["-c"]'] 69 | self.assertEqual(d.find_entrypoint_or_cmd(), (["/bin/sh"], ["-c"])) 70 | 71 | def test_find_entrypoint_or_cmd_cmd_only(self): 72 | d = Dockerfile("Dockerfile") 73 | d._Dockerfile__dockerfile = ['FROM ubuntu', 'CMD ["/bin/sh", "-c", "-x"]'] 74 | self.assertEqual(d.find_entrypoint_or_cmd(), (None, ["/bin/sh", "-c", "-x"])) 75 | 76 | def test_find_entrypoint_or_cmd_entrypoint_only(self): 77 | d = Dockerfile("Dockerfile") 78 | d._Dockerfile__dockerfile = ['FROM ubuntu', 'ENTRYPOINT ["/bin/sh"]'] 79 | self.assertEqual(d.find_entrypoint_or_cmd(), (["/bin/sh"], None)) 80 | 81 | def test_find_entrypoint_or_cmd_none(self): 82 | d = Dockerfile("Dockerfile") 83 | d._Dockerfile__dockerfile = ['FROM ubuntu'] 84 | self.assertEqual(d.find_entrypoint_or_cmd(), (None, None)) 85 | -------------------------------------------------------------------------------- /docs/getting-started/step-2-deal-with-dynamic-configuration.md: -------------------------------------------------------------------------------- 1 | ### [Back: 1. First step](step-1-first-step.md) 2 | 3 | 2. Deal with dynamic configuration 4 | ================================== 5 | 6 | ## Static files and mapping.py 7 | 8 | Nginx keeps its site configurations in `/etc/nginx/sites-available` directory. We need to create `site-example.conf` in this directory and put simple site configuration in it. Tow provides an ability to do this. Create new file `files/site-example.conf` 9 | 10 | ```Nginx 11 | server { 12 | listen 80 default_server; 13 | listen [::]:80 default_server ipv6only=on; 14 | 15 | root /usr/share/nginx/html; 16 | index index.html index.htm; 17 | 18 | # Make site accessible from http://localhost/ 19 | server_name localhost; 20 | 21 | location / { 22 | # First attempt to serve request as file, then 23 | # as directory, then fall back to displaying a 404. 24 | try_files $uri $uri/ /index.html; 25 | # Uncomment to enable naxsi on this location 26 | # include /etc/nginx/naxsi.rules 27 | } 28 | } 29 | ``` 30 | 31 | Add record into `mapping.py` with local file specification and path to place file inside the container. Open `mapping.py` with your favorite text editor and update it this way. 32 | 33 | ```python 34 | mapping = { 35 | "templates": [] 36 | "files":[ 37 | ("site-example.conf", "/etc/nginx/sites-available/site-example.conf") 38 | ] 39 | } 40 | ``` 41 | 42 | Let's build container and check how it works. 43 | 44 | ```console 45 | $ tow build -t tow-nginx-example 46 | ... 47 | Successfully built 2ac717478a38 48 | $ docker run -d -p 8080:80 tow-nginx-example 49 | $ curl localhost:8080 50 | ... 51 |

Welcome to nginx on Debian!

52 | ... 53 | ``` 54 | 55 | Now we got Nginx up and running and default webpage served. Let's add our custom webpage with a little bit of dynamic content. 56 | 57 | ## Add some dynamics content 58 | 59 | To add any dynamics content Tow provides templates and attributes. Attributes store any values in declared variables in `.py` files inside `attributes` directory. By default Tow creates `attributes/default.py` file for storing attributes. 60 | 61 | Open `attributes/default.py` and add variable for our webpage header. 62 | 63 | ```python 64 | header = "Tow Demo Webpage" 65 | ``` 66 | 67 | Now let's go and create simple webpage template. Create `templates/index.html.tmpl` 68 | 69 | ```html 70 | 71 | 72 | 73 | 74 | {{header}} 75 | 76 | 77 | 78 |

{{header}}

79 | 80 | 81 | 82 | ``` 83 | 84 | In this template we used `header` attribute to set title and header of our webpage. To pass this template inside container add it to `mapping.py` as we did it for static files. By default Nginx uses `/var/www/html` to store site content, so modify `mapping.py` this way: 85 | 86 | ```python 87 | mapping = { 88 | "templates": [ 89 | ("index.html.tmpl", "/var/www/html/index.html") 90 | ], 91 | "files": [ 92 | ("site-example.conf", "/etc/nginx/sites-available/site-example.conf") 93 | ] 94 | } 95 | ``` 96 | 97 | Attributes and templates provide simple, but powerful way to store configuration variables and configuration files templates structured and separately. 98 | 99 | ## Using environment variables 100 | 101 | To modify attributes while running container from image we need to use environment variables. First of all modify `Dockerfile`: 102 | 103 | ```Dockerfile 104 | FROM debian:jessie 105 | 106 | RUN apt-get update && \ 107 | apt-get install -y nginx 108 | 109 | RUN rm -rf /var/lib/apt/lists/* && \ 110 | chown -R www-data:www-data /var/lib/nginx 111 | 112 | VOLUME /var/www/html 113 | WORKDIR /etc/nginx 114 | EXPOSE 80 115 | 116 | ENV CONTENT This text from the CONTENT variable 117 | 118 | CMD ["nginx", "-g", "daemon off;"] 119 | ``` 120 | 121 | Let's add additional attribute for webpage content which will use value of `CONTENT` environment variable. Open `attributes/default.py`. 122 | 123 | ```python 124 | header = "Tow Demo Webpage" 125 | content = env["CONTENT"] 126 | ``` 127 | 128 | Modify webpage template `templates/index.html.tmpl` 129 | 130 | ```html 131 | 132 | 133 | 134 | {{header}} 135 | 136 | 137 | 138 |

{{header}}

139 |

{{content}}

140 | 141 | 142 | 143 | ``` 144 | 145 | ## Run and override 146 | 147 | Finally build and try out our latest image. 148 | 149 | ```console 150 | $ tow build -t tow-nginx-example 151 | $ tow run -d -p 8080:80 --name tow-nginx-example tow-nginx-example 152 | $ curl localhost:8080 153 | 154 | 155 | 156 | Tow Demo Webpage 157 | 158 | 159 | 160 |

Tow Demo Webpage

161 |

This text from the CONTENT variable

162 | 163 | 164 | 165 | ``` 166 | 167 | Well done! We got processed `index.html` with values from `header` and `content` attributes. Try out to override `CONTENT` environment variable. 168 | 169 | ```console 170 | $ docker rm -f tow-nginx-example 171 | $ tow run -d -p 8080:80 -e CONTENT="Override default CONTENT" --name tow-nginx-example tow-nginx-example 172 | $ curl localhost:8080 173 | 174 | 175 | 176 | Tow Demo Webpage 177 | 178 | 179 | 180 |

Tow Demo Webpage

181 |

Override default CONTENT

182 | 183 | 184 | 185 | ``` 186 | 187 | And we've got a webpage with new content set in CONTENT env variable on `tow run` command. Now you can create Tow project by yourself and make configuration management for containers much easier. 188 | 189 | If you want to get deep understanding of how `tow build` and `tow run` works checkout the next step. 190 | 191 | ### [Next: 3. Understanding Build and Run Commands](#) 192 | -------------------------------------------------------------------------------- /tow/dockerfile.py: -------------------------------------------------------------------------------- 1 | """ 2 | TODO: add comments 3 | """ 4 | import re 5 | 6 | 7 | class Dockerfile(object): 8 | 9 | def __init__(self, dockerfile_path): 10 | self.__dockerfile_path = dockerfile_path 11 | self.__dockerfile = None 12 | self.__envs = {} 13 | 14 | def _parse_dockerfile(self): 15 | if not self.__dockerfile: 16 | with open(self.__dockerfile_path, "r") as df: 17 | self.__dockerfile = [line.strip() for line in df.readlines()] 18 | 19 | def _parse_spaced_envs(self, env_line): 20 | envs = env_line.split(" ") 21 | env_name = envs[0] 22 | env_var = " ".join(envs[1:]) 23 | return {env_name: env_var} 24 | 25 | def _parse_multiline_envs(self, env_line): 26 | result = {} 27 | while env_line and env_line != "\\": 28 | equal_index = re.search("\w+=", env_line) 29 | env_name = equal_index.group().split("=")[0] 30 | env_line = env_line[len(equal_index.group()):] 31 | # if starts from " parse text between quotes 32 | if env_line.startswith("\""): 33 | env_var = re.search("([\"'])(?:(?=(\\\\?))\\2.)*?\\1", 34 | env_line).group() 35 | env_line = env_line[len(env_var):] 36 | # remove around quotes 37 | env_var = re.sub(r'^"|"$', '', env_var) 38 | else: 39 | # read until space 40 | env_var_match = re.search(".*([^\\\\]\\s)", 41 | env_line) 42 | # if space doesn't found that mean that it's the last 43 | # variable in string 44 | if not env_var_match: 45 | env_var = env_line 46 | else: 47 | env_var = env_var_match.group() 48 | env_line = env_line[len(env_var):] 49 | env_var = env_var.strip() 50 | 51 | env_line = env_line.strip() 52 | result[env_name] = env_var 53 | 54 | is_multiline_envs = env_line.endswith("\\") 55 | return (result, is_multiline_envs) 56 | 57 | def _parse_exec_line(self, exec_line): 58 | if exec_line: 59 | command = exec_line[exec_line.find(" ") + 1:] 60 | # Handle array command 61 | if command.startswith("[") and command.endswith("]"): 62 | command = command[1:-1] 63 | return [sh.strip()[1:-1] for sh in command.split(",")] 64 | else: # It's just shell notation 65 | return [command.strip()] 66 | return None 67 | 68 | def envs(self): 69 | if not self.__envs: 70 | self._parse_dockerfile() 71 | 72 | is_multiline_envs = False 73 | for dockerfile_line in self.__dockerfile: 74 | env_vars = {} 75 | dl = dockerfile_line.strip() 76 | if is_multiline_envs: 77 | (env_vars, is_multiline_envs) = \ 78 | self._parse_multiline_envs(dl) 79 | elif dl.startswith("ENV"): 80 | # First of all parse single line env variables with 81 | # space separator 82 | env_vars_line = dl[len("ENV"):].strip() 83 | if not re.match("^\\w+=", env_vars_line): 84 | env_vars = self._parse_spaced_envs(env_vars_line) 85 | else: 86 | (env_vars, is_multiline_envs) = \ 87 | self._parse_multiline_envs(env_vars_line) 88 | 89 | self.__envs.update(env_vars) 90 | return self.__envs 91 | 92 | def add_copy(self, file_mapping): 93 | copy_block = [] 94 | for fm in file_mapping: 95 | src = fm[0] 96 | dst = fm[1] 97 | mask = fm[2] if len(fm) > 2 else None 98 | copy_block.append("COPY %s %s" % (src, dst)) 99 | if mask: 100 | copy_block.append("RUN chmod %s %s" % (mask, dst)) 101 | 102 | if copy_block: 103 | copy_block = ["# TOW COPY BLOCK FROM MAPPING FILE START"] +\ 104 | copy_block +\ 105 | ["# TOW COPY BLOCK FROM MAPPING FILE END"] 106 | 107 | position = len(self.__dockerfile) 108 | 109 | for i, dockerfile_line in enumerate(self.__dockerfile): 110 | if dockerfile_line.startswith("FROM") or dockerfile_line.startswith("MAINTAINER"): 111 | position = i 112 | 113 | position = position + 1 114 | self.__dockerfile = self.__dockerfile[:position] + copy_block + self.__dockerfile[position:] 115 | 116 | def find_entrypoint_or_cmd(self): 117 | """ This command find entrypoint or cmd blocks and 118 | return them in shell style 119 | """ 120 | cmd = None 121 | entrypoint = None 122 | for dockerfile_line in self.__dockerfile: 123 | if dockerfile_line.startswith("CMD"): 124 | cmd = dockerfile_line 125 | elif dockerfile_line.startswith("ENTRYPOINT"): 126 | entrypoint = dockerfile_line 127 | return (self._parse_exec_line(entrypoint), self._parse_exec_line(cmd)) 128 | 129 | def replace_entrypoint_or_cmd_by_tow_cmd(self, cmd): 130 | self.__dockerfile = [dockerfile_line 131 | for dockerfile_line in self.__dockerfile 132 | if (not dockerfile_line.startswith("CMD") and 133 | not dockerfile_line.startswith("ENTRYPOINT"))] 134 | 135 | position = len(self.__dockerfile) 136 | for i, dockerfile_line in enumerate(self.__dockerfile): 137 | if dockerfile_line.startswith("EXPOSE"): 138 | position = i 139 | break 140 | self.__dockerfile = self.__dockerfile[:position] + \ 141 | ["ENTRYPOINT %s" % cmd] + self.__dockerfile[position:] 142 | 143 | def save(self, dockerfile_path): 144 | with open(dockerfile_path, "w+") as df: 145 | df.write("\n".join(self.__dockerfile)) 146 | -------------------------------------------------------------------------------- /tow/commands/utils.py: -------------------------------------------------------------------------------- 1 | """ 2 | TODO: add comments 3 | """ 4 | import os 5 | import shutil 6 | from tow.modules import load_module 7 | from tow.dockerfile import Dockerfile 8 | from tow.attrs import process_attrs 9 | from tow import templates 10 | import subprocess 11 | import json 12 | import collections 13 | 14 | 15 | TOW_VOLUME = "/tow" 16 | 17 | 18 | def project_paths(): 19 | current_dir = os.getcwd() 20 | return (current_dir, 21 | os.path.join(current_dir, "Dockerfile"), 22 | os.path.join(current_dir, "mapping.py"), 23 | os.path.join(current_dir, "templates"), 24 | os.path.join(current_dir, "files"), 25 | os.path.join(current_dir, "attributes")) 26 | 27 | 28 | def prepare_workingdir(workingdir): 29 | if os.path.exists(workingdir): 30 | shutil.rmtree(workingdir, ignore_errors=True) 31 | os.mkdir(workingdir) 32 | 33 | 34 | def dequote(s): 35 | """ 36 | If a string has single or double quotes around it, remove them. 37 | Make sure the pair of quotes match. 38 | If a matching pair of quotes is not found, return the string unchanged. 39 | """ 40 | if (s[0] == s[-1]) and s.startswith(("'", '"')): 41 | return s[1:-1] 42 | return s 43 | 44 | 45 | def parse_env_arg(env_arg): 46 | if env_arg: 47 | env_pair = env_arg.split("=") 48 | if len(env_pair) < 2: 49 | return (env_pair[0], os.getenv(env_pair[0], "")) 50 | else: 51 | return (env_pair[0], dequote("".join(env_pair[1:]))) 52 | 53 | 54 | def parse_envfile(env_file_name): 55 | envs = [] 56 | with open(env_file_name, "r") as envfile: 57 | for envfile_line in envfile.readlines(): 58 | if not envfile_line.strip().startswith("#"): 59 | envs.append(parse_env_arg(envfile_line.strip())) 60 | return envs 61 | 62 | 63 | def get_env_args(args): 64 | envs = {} 65 | 66 | i = 0 67 | while i < len(args): 68 | arg = args[i] 69 | if arg == "-e": 70 | i = i + 1 71 | (env_name, env_var) = parse_env_arg(args[i].strip()) 72 | envs[env_name] = env_var 73 | i = i + 1 74 | elif arg == "--env": 75 | i = i + 1 76 | while i < len(args) and not args[i].startswith("-"): 77 | (env_name, env_var) = parse_env_arg(args[i].strip()) 78 | envs[env_name] = env_var 79 | i = i + 1 80 | elif arg == "--env-file": 81 | i = i + 1 82 | env_file_name = args[i].strip() 83 | env_vars = parse_envfile(env_file_name) 84 | envs.update({env_var: env_name for (env_name, env_var) in env_vars}) 85 | i = i + 1 86 | else: 87 | i = i + 1 88 | return envs 89 | 90 | 91 | def copy_files(workingdir, files_path, mapping): 92 | for fm in mapping.get("files", []): 93 | src = fm[0] 94 | src_file_path = os.path.join(files_path, src) 95 | if os.path.exists(src_file_path): 96 | dst_file_path = os.path.join(workingdir, src) 97 | file_path_dir = os.path.dirname(dst_file_path) 98 | if not os.path.exists(file_path_dir): 99 | os.makedirs(file_path_dir) 100 | shutil.copy2(src_file_path, dst_file_path) 101 | else: 102 | print "file %s doesn't exists" % src_file_path 103 | 104 | 105 | def init_tow(env_args={}, attributes_name="default", mapping_name="mapping"): 106 | (current_dir, dockerfile_path, 107 | mappingfile_path, templates_path, 108 | files_path, attributes_path) = project_paths() 109 | 110 | file_mapping = load_module({}, current_dir, "mapping") 111 | mapping = getattr(file_mapping, mapping_name, {}) 112 | 113 | workingdir = os.path.join(current_dir, ".tow") 114 | 115 | prepare_workingdir(workingdir) 116 | 117 | dockerfile = Dockerfile(dockerfile_path) 118 | 119 | # TODO: print warn into logs that you try to read env but it is not defined 120 | envs = collections.defaultdict(lambda: "") 121 | envs.update(dockerfile.envs()) 122 | # envs passed as params has more priority then Dockerfile envs 123 | envs.update(env_args) 124 | attrs = process_attrs(envs, attributes_path, attributes_name) 125 | 126 | # process templates 127 | for fm in mapping.get("templates", []): 128 | src = fm[0] 129 | src_template_path = os.path.join(templates_path, src) 130 | if os.path.exists(src_template_path): 131 | processed_template_path = os.path.join(workingdir, src) 132 | template_path_dir = os.path.dirname(processed_template_path) 133 | if not os.path.exists(template_path_dir): 134 | os.makedirs(template_path_dir) 135 | 136 | templates.process(os.path.dirname(src_template_path), 137 | os.path.basename(src_template_path), 138 | processed_template_path, attrs) 139 | else: 140 | print "WARN: template file %s doesn't exists" % src_template_path 141 | 142 | copy_files(workingdir, files_path, mapping) 143 | 144 | # Transform dict with mapping to list of file tuples that exists in .tow dir 145 | handled_file_mapping = [fm for fm_list in mapping.values() 146 | for fm in fm_list 147 | if os.path.exists(os.path.join(workingdir, fm[0]))] 148 | 149 | # Init mapping file 150 | templates.process_template("mapping.sh.tmpl", 151 | os.path.join(workingdir, "mapping.sh"), 152 | {"mapping": handled_file_mapping, 153 | "volume_name": TOW_VOLUME}) 154 | 155 | return (handled_file_mapping, dockerfile, envs, attrs, workingdir) 156 | 157 | 158 | def get_link_envs(name, alias, current_name): 159 | envs = {} 160 | p = subprocess.Popen(["docker", "inspect", name], 161 | stdout=subprocess.PIPE, stderr=subprocess.PIPE) 162 | out, err = p.communicate() 163 | if err: 164 | print "ERROR: Problem to make docker inspect for %s, %s" % (name, err) 165 | return 166 | linked_info = json.loads(out.strip(), object_pairs_hook=collections.OrderedDict) 167 | exposed_ports = [port for port in linked_info[0]["Config"]["ExposedPorts"].keys()] 168 | linked_envs = [env for env in linked_info[0]["Config"]["Env"] 169 | if not (env.startswith("HOME") or env.startswith("PATH"))] 170 | ip_address = linked_info[0]["NetworkSettings"]["IPAddress"] 171 | if current_name: 172 | envs["%s_NAME" % alias.upper()] = "/%s/%s" % (current_name, alias) 173 | else: 174 | print """You don't declare container name that why we could not 175 | process _NAME env variable""" 176 | if exposed_ports: 177 | firts_port, first_proto = exposed_ports[0].split("/") 178 | envs["%s_PORT" % alias.upper()] = "%s://%s:%s" % (first_proto, 179 | ip_address, 180 | firts_port) 181 | for exposed_port in exposed_ports: 182 | port, proto = exposed_port.split("/") 183 | envs["%s_PORT_%s_%s" % (alias.upper(), port, proto.upper())] = "%s://%s:%s" % (proto, 184 | ip_address, 185 | port) 186 | envs["%s_PORT_%s_%s_PROTO" % (alias.upper(), port, proto.upper())] = proto 187 | envs["%s_PORT_%s_%s_PORT" % (alias.upper(), port, proto.upper())] = port 188 | envs["%s_PORT_%s_%s_ADDR" % (alias.upper(), port, proto.upper())] = ip_address 189 | 190 | if linked_envs: 191 | for linked_env in linked_envs: 192 | linked_env_name = linked_env.split("=")[0] 193 | linked_env_value = linked_env[len(linked_env_name + "="):] 194 | envs["%s_ENV_%s" % (alias.upper(), linked_env_name)] = linked_env_value 195 | return envs 196 | 197 | 198 | def get_linked_container_variables(args): 199 | envs = {} 200 | current_name = None 201 | if "--name" in args: 202 | current_name_idx = args.index("--name") 203 | current_name_idx = current_name_idx + 1 204 | current_name = args[current_name_idx] 205 | 206 | has_link = False 207 | for arg in args: 208 | if arg == "--link": 209 | has_link = True 210 | continue 211 | elif arg.startswith("--link"): 212 | link_info = arg.split("=")[1] 213 | (name, alias) = link_info.split(":") 214 | envs.update(get_link_envs(name, alias, current_name)) 215 | elif has_link: 216 | has_link = False 217 | (name, alias) = arg.split(":") 218 | envs.update(get_link_envs(name, alias, current_name)) 219 | return envs 220 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | 203 | --------------------------------------------------------------------------------