From 461b600068465fb9260f3918ba12e8d501986515 Mon Sep 17 00:00:00 2001 From: Aanand Prasad Date: Tue, 31 Mar 2015 15:23:34 -0400 Subject: [PATCH 01/19] Merge pull request #1225 from aanand/fix-1222 When extending, `build` replaces `image` and vice versa (cherry picked from commit 6dbe321a45dfd7539234f889825b54e1a026e46f) Signed-off-by: Aanand Prasad --- compose/config.py | 6 ++++++ tests/unit/config_test.py | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/compose/config.py b/compose/config.py index 0cd7c1ae..8d6ffea7 100644 --- a/compose/config.py +++ b/compose/config.py @@ -189,6 +189,12 @@ def merge_service_dicts(base, override): override.get('volumes'), ) + if 'image' in override and 'build' in d: + del d['build'] + + if 'build' in override and 'image' in d: + del d['image'] + for k in ALLOWED_KEYS: if k not in ['environment', 'volumes']: if k in override: diff --git a/tests/unit/config_test.py b/tests/unit/config_test.py index 8deb457a..67f24a92 100644 --- a/tests/unit/config_test.py +++ b/tests/unit/config_test.py @@ -79,6 +79,39 @@ class MergeTest(unittest.TestCase): ) self.assertEqual(set(service_dict['volumes']), set(['/bar:/code', '/data'])) + def test_merge_build_or_image_no_override(self): + self.assertEqual( + config.merge_service_dicts({'build': '.'}, {}), + {'build': '.'}, + ) + + self.assertEqual( + config.merge_service_dicts({'image': 'redis'}, {}), + {'image': 'redis'}, + ) + + def test_merge_build_or_image_override_with_same(self): + self.assertEqual( + config.merge_service_dicts({'build': '.'}, {'build': './web'}), + {'build': './web'}, + ) + + self.assertEqual( + config.merge_service_dicts({'image': 'redis'}, {'image': 'postgres'}), + {'image': 'postgres'}, + ) + + def test_merge_build_or_image_override_with_other(self): + self.assertEqual( + config.merge_service_dicts({'build': '.'}, {'image': 'redis'}), + {'image': 'redis'} + ) + + self.assertEqual( + config.merge_service_dicts({'image': 'redis'}, {'build': '.'}), + {'build': '.'} + ) + class EnvTest(unittest.TestCase): def test_parse_environment_as_list(self): From b24a60ba9fdbfc9d3bdade6efbc9b629cdd4872b Mon Sep 17 00:00:00 2001 From: Aanand Prasad Date: Tue, 31 Mar 2015 16:01:22 -0400 Subject: [PATCH 02/19] Merge pull request #1226 from aanand/merge-multi-value-options Merge multi-value options when extending (cherry picked from commit e708f4f59dcb417e90a5bbdcadcee37e8c6b7802) Signed-off-by: Aanand Prasad --- compose/config.py | 30 ++++++++++++++--- tests/unit/config_test.py | 71 +++++++++++++++++++++++++++++++++++---- 2 files changed, 90 insertions(+), 11 deletions(-) diff --git a/compose/config.py b/compose/config.py index 8d6ffea7..7f2e302b 100644 --- a/compose/config.py +++ b/compose/config.py @@ -195,10 +195,23 @@ def merge_service_dicts(base, override): if 'build' in override and 'image' in d: del d['image'] - for k in ALLOWED_KEYS: - if k not in ['environment', 'volumes']: - if k in override: - d[k] = override[k] + list_keys = ['ports', 'expose', 'external_links'] + + for key in list_keys: + if key in base or key in override: + d[key] = base.get(key, []) + override.get(key, []) + + list_or_string_keys = ['dns', 'dns_search'] + + for key in list_or_string_keys: + if key in base or key in override: + d[key] = to_list(base.get(key)) + to_list(override.get(key)) + + already_merged_keys = ['environment', 'volumes'] + list_keys + list_or_string_keys + + for k in set(ALLOWED_KEYS) - set(already_merged_keys): + if k in override: + d[k] = override[k] return d @@ -354,6 +367,15 @@ def expand_path(working_dir, path): return os.path.abspath(os.path.join(working_dir, path)) +def to_list(value): + if value is None: + return [] + elif isinstance(value, six.string_types): + return [value] + else: + return value + + def get_service_name_from_net(net_config): if not net_config: return diff --git a/tests/unit/config_test.py b/tests/unit/config_test.py index 67f24a92..d95ba783 100644 --- a/tests/unit/config_test.py +++ b/tests/unit/config_test.py @@ -39,40 +39,40 @@ class ConfigTest(unittest.TestCase): config.make_service_dict('foo', {'ports': ['8000']}) -class MergeTest(unittest.TestCase): - def test_merge_volumes_empty(self): +class MergeVolumesTest(unittest.TestCase): + def test_empty(self): service_dict = config.merge_service_dicts({}, {}) self.assertNotIn('volumes', service_dict) - def test_merge_volumes_no_override(self): + def test_no_override(self): service_dict = config.merge_service_dicts( {'volumes': ['/foo:/code', '/data']}, {}, ) self.assertEqual(set(service_dict['volumes']), set(['/foo:/code', '/data'])) - def test_merge_volumes_no_base(self): + def test_no_base(self): service_dict = config.merge_service_dicts( {}, {'volumes': ['/bar:/code']}, ) self.assertEqual(set(service_dict['volumes']), set(['/bar:/code'])) - def test_merge_volumes_override_explicit_path(self): + def test_override_explicit_path(self): service_dict = config.merge_service_dicts( {'volumes': ['/foo:/code', '/data']}, {'volumes': ['/bar:/code']}, ) self.assertEqual(set(service_dict['volumes']), set(['/bar:/code', '/data'])) - def test_merge_volumes_add_explicit_path(self): + def test_add_explicit_path(self): service_dict = config.merge_service_dicts( {'volumes': ['/foo:/code', '/data']}, {'volumes': ['/bar:/code', '/quux:/data']}, ) self.assertEqual(set(service_dict['volumes']), set(['/bar:/code', '/quux:/data'])) - def test_merge_volumes_remove_explicit_path(self): + def test_remove_explicit_path(self): service_dict = config.merge_service_dicts( {'volumes': ['/foo:/code', '/quux:/data']}, {'volumes': ['/bar:/code', '/data']}, @@ -113,6 +113,63 @@ class MergeTest(unittest.TestCase): ) +class MergeListsTest(unittest.TestCase): + def test_empty(self): + service_dict = config.merge_service_dicts({}, {}) + self.assertNotIn('ports', service_dict) + + def test_no_override(self): + service_dict = config.merge_service_dicts( + {'ports': ['10:8000', '9000']}, + {}, + ) + self.assertEqual(set(service_dict['ports']), set(['10:8000', '9000'])) + + def test_no_base(self): + service_dict = config.merge_service_dicts( + {}, + {'ports': ['10:8000', '9000']}, + ) + self.assertEqual(set(service_dict['ports']), set(['10:8000', '9000'])) + + def test_add_item(self): + service_dict = config.merge_service_dicts( + {'ports': ['10:8000', '9000']}, + {'ports': ['20:8000']}, + ) + self.assertEqual(set(service_dict['ports']), set(['10:8000', '9000', '20:8000'])) + + +class MergeStringsOrListsTest(unittest.TestCase): + def test_no_override(self): + service_dict = config.merge_service_dicts( + {'dns': '8.8.8.8'}, + {}, + ) + self.assertEqual(set(service_dict['dns']), set(['8.8.8.8'])) + + def test_no_base(self): + service_dict = config.merge_service_dicts( + {}, + {'dns': '8.8.8.8'}, + ) + self.assertEqual(set(service_dict['dns']), set(['8.8.8.8'])) + + def test_add_string(self): + service_dict = config.merge_service_dicts( + {'dns': ['8.8.8.8']}, + {'dns': '9.9.9.9'}, + ) + self.assertEqual(set(service_dict['dns']), set(['8.8.8.8', '9.9.9.9'])) + + def test_add_list(self): + service_dict = config.merge_service_dicts( + {'dns': '8.8.8.8'}, + {'dns': ['9.9.9.9']}, + ) + self.assertEqual(set(service_dict['dns']), set(['8.8.8.8', '9.9.9.9'])) + + class EnvTest(unittest.TestCase): def test_parse_environment_as_list(self): environment =[ From e4e802d1f86ceb16d45d0176f94906e799f90fc9 Mon Sep 17 00:00:00 2001 From: Daniel Nephin Date: Tue, 31 Mar 2015 21:20:02 -0400 Subject: [PATCH 03/19] Merge pull request #1213 from moysesb/relative_build Make value of 'build:' relative to the yml file. (cherry picked from commit 0f70b8638ff7167e9755d24dc8dab1579662f72d) Signed-off-by: Aanand Prasad --- compose/config.py | 14 ++++++++ docs/yml.md | 5 +-- tests/fixtures/build-ctx/Dockerfile | 2 ++ tests/fixtures/build-path/docker-compose.yml | 2 ++ .../docker-compose.yml | 2 +- .../simple-dockerfile/docker-compose.yml | 2 +- tests/unit/config_test.py | 33 +++++++++++++++++++ 7 files changed, 56 insertions(+), 4 deletions(-) create mode 100644 tests/fixtures/build-ctx/Dockerfile create mode 100644 tests/fixtures/build-path/docker-compose.yml diff --git a/compose/config.py b/compose/config.py index 7f2e302b..1dc64af2 100644 --- a/compose/config.py +++ b/compose/config.py @@ -171,6 +171,9 @@ def process_container_options(service_dict, working_dir=None): if 'volumes' in service_dict: service_dict['volumes'] = resolve_host_paths(service_dict['volumes'], working_dir=working_dir) + if 'build' in service_dict: + service_dict['build'] = resolve_build_path(service_dict['build'], working_dir=working_dir) + return service_dict @@ -330,6 +333,17 @@ def resolve_host_path(volume, working_dir): return container_path +def resolve_build_path(build_path, working_dir=None): + if working_dir is None: + raise Exception("No working_dir passed to resolve_build_path") + + _path = expand_path(working_dir, build_path) + if not os.path.exists(_path) or not os.access(_path, os.R_OK): + raise ConfigurationError("build path %s either does not exist or is not accessible." % _path) + else: + return _path + + def merge_volumes(base, override): d = dict_from_volumes(base) d.update(dict_from_volumes(override)) diff --git a/docs/yml.md b/docs/yml.md index 157ba4e6..a9909e81 100644 --- a/docs/yml.md +++ b/docs/yml.md @@ -29,8 +29,9 @@ image: a4bc65fd ### build -Path to a directory containing a Dockerfile. This directory is also the -build context that is sent to the Docker daemon. +Path to a directory containing a Dockerfile. When the value supplied is a +relative path, it is interpreted as relative to the location of the yml file +itself. This directory is also the build context that is sent to the Docker daemon. Compose will build and tag it with a generated name, and use that image thereafter. diff --git a/tests/fixtures/build-ctx/Dockerfile b/tests/fixtures/build-ctx/Dockerfile new file mode 100644 index 00000000..d1ceac6b --- /dev/null +++ b/tests/fixtures/build-ctx/Dockerfile @@ -0,0 +1,2 @@ +FROM busybox:latest +CMD echo "success" diff --git a/tests/fixtures/build-path/docker-compose.yml b/tests/fixtures/build-path/docker-compose.yml new file mode 100644 index 00000000..66e8916e --- /dev/null +++ b/tests/fixtures/build-path/docker-compose.yml @@ -0,0 +1,2 @@ +foo: + build: ../build-ctx/ diff --git a/tests/fixtures/dockerfile_with_entrypoint/docker-compose.yml b/tests/fixtures/dockerfile_with_entrypoint/docker-compose.yml index a1038118..78631502 100644 --- a/tests/fixtures/dockerfile_with_entrypoint/docker-compose.yml +++ b/tests/fixtures/dockerfile_with_entrypoint/docker-compose.yml @@ -1,2 +1,2 @@ service: - build: tests/fixtures/dockerfile_with_entrypoint + build: . diff --git a/tests/fixtures/simple-dockerfile/docker-compose.yml b/tests/fixtures/simple-dockerfile/docker-compose.yml index a3f56d46..b0357541 100644 --- a/tests/fixtures/simple-dockerfile/docker-compose.yml +++ b/tests/fixtures/simple-dockerfile/docker-compose.yml @@ -1,2 +1,2 @@ simple: - build: tests/fixtures/simple-dockerfile + build: . diff --git a/tests/unit/config_test.py b/tests/unit/config_test.py index d95ba783..f25f3a9d 100644 --- a/tests/unit/config_test.py +++ b/tests/unit/config_test.py @@ -381,3 +381,36 @@ class ExtendsTest(unittest.TestCase): ] self.assertEqual(set(dicts[0]['volumes']), set(paths)) + + +class BuildPathTest(unittest.TestCase): + def setUp(self): + self.abs_context_path = os.path.join(os.getcwd(), 'tests/fixtures/build-ctx') + + def test_nonexistent_path(self): + options = {'build': 'nonexistent.path'} + self.assertRaises( + config.ConfigurationError, + lambda: config.make_service_dict('foo', options, 'tests/fixtures/build-path'), + ) + + def test_relative_path(self): + relative_build_path = '../build-ctx/' + service_dict = config.make_service_dict( + 'relpath', + {'build': relative_build_path}, + working_dir='tests/fixtures/build-path' + ) + self.assertEquals(service_dict['build'], self.abs_context_path) + + def test_absolute_path(self): + service_dict = config.make_service_dict( + 'abspath', + {'build': self.abs_context_path}, + working_dir='tests/fixtures/build-path' + ) + self.assertEquals(service_dict['build'], self.abs_context_path) + + def test_from_file(self): + service_dict = config.load('tests/fixtures/build-path/docker-compose.yml') + self.assertEquals(service_dict, [{'name': 'foo', 'build': self.abs_context_path}]) From 78227c3c068a3ca7be47d3104fceb8c1e065e078 Mon Sep 17 00:00:00 2001 From: Aanand Prasad Date: Fri, 27 Mar 2015 14:59:49 -0700 Subject: [PATCH 04/19] Merge pull request #1202 from aanand/jenkins-script WIP: Jenkins script (cherry picked from commit 853ce255eac5375562e399d3e105dc5a456dbb99) Signed-off-by: Aanand Prasad --- Dockerfile | 3 +++ script/build-linux | 18 +++++++++++------- script/build-linux-inner | 10 ++++++++++ script/ci | 18 ++++++++++++++++++ script/test-versions | 5 +---- script/wrapdocker | 2 +- 6 files changed, 44 insertions(+), 12 deletions(-) create mode 100755 script/build-linux-inner create mode 100755 script/ci diff --git a/Dockerfile b/Dockerfile index d7a6019a..8ec05cc9 100644 --- a/Dockerfile +++ b/Dockerfile @@ -23,6 +23,9 @@ RUN set -ex; \ chmod +x /usr/local/bin/docker-$v; \ done +# Set the default Docker to be run +RUN ln -s /usr/local/bin/docker-1.3.3 /usr/local/bin/docker + RUN useradd -d /home/user -m -s /bin/bash user WORKDIR /code/ diff --git a/script/build-linux b/script/build-linux index 07c9d7ec..5e4a9470 100755 --- a/script/build-linux +++ b/script/build-linux @@ -1,8 +1,12 @@ -#!/bin/sh +#!/bin/bash + set -ex -mkdir -p `pwd`/dist -chmod 777 `pwd`/dist -docker build -t docker-compose . -docker run -u user -v `pwd`/dist:/code/dist --rm --entrypoint pyinstaller docker-compose -F bin/docker-compose -mv dist/docker-compose dist/docker-compose-Linux-x86_64 -docker run -u user -v `pwd`/dist:/code/dist --rm --entrypoint dist/docker-compose-Linux-x86_64 docker-compose --version + +TAG="docker-compose" +docker build -t "$TAG" . +docker run \ + --rm \ + --user=user \ + --volume="$(pwd):/code" \ + --entrypoint="script/build-linux-inner" \ + "$TAG" diff --git a/script/build-linux-inner b/script/build-linux-inner new file mode 100755 index 00000000..34b0c06f --- /dev/null +++ b/script/build-linux-inner @@ -0,0 +1,10 @@ +#!/bin/bash + +set -ex + +mkdir -p `pwd`/dist +chmod 777 `pwd`/dist + +pyinstaller -F bin/docker-compose +mv dist/docker-compose dist/docker-compose-Linux-x86_64 +dist/docker-compose-Linux-x86_64 --version diff --git a/script/ci b/script/ci new file mode 100755 index 00000000..a1391c62 --- /dev/null +++ b/script/ci @@ -0,0 +1,18 @@ +#!/bin/bash +# This should be run inside a container built from the Dockerfile +# at the root of the repo: +# +# $ TAG="docker-compose:$(git rev-parse --short HEAD)" +# $ docker build -t "$TAG" . +# $ docker run --rm --volume="/var/run/docker.sock:/var/run/docker.sock" --volume="$(pwd)/.git:/code/.git" -e "TAG=$TAG" --entrypoint="script/ci" "$TAG" + +set -e + +>&2 echo "Validating DCO" +script/validate-dco + +export DOCKER_VERSIONS=all +. script/test-versions + +>&2 echo "Building Linux binary" +su -c script/build-linux-inner user diff --git a/script/test-versions b/script/test-versions index a9e3bc4c..a172b9a3 100755 --- a/script/test-versions +++ b/script/test-versions @@ -4,9 +4,6 @@ set -e ->&2 echo "Validating DCO" -script/validate-dco - >&2 echo "Running lint checks" flake8 compose @@ -18,7 +15,7 @@ fi for version in $DOCKER_VERSIONS; do >&2 echo "Running tests against Docker $version" - docker-1.5.0 run \ + docker run \ --rm \ --privileged \ --volume="/var/lib/docker" \ diff --git a/script/wrapdocker b/script/wrapdocker index 20dc9e3c..7b699688 100755 --- a/script/wrapdocker +++ b/script/wrapdocker @@ -4,7 +4,7 @@ if [ "$DOCKER_VERSION" == "" ]; then DOCKER_VERSION="1.5.0" fi -ln -s "/usr/local/bin/docker-$DOCKER_VERSION" "/usr/local/bin/docker" +ln -fs "/usr/local/bin/docker-$DOCKER_VERSION" "/usr/local/bin/docker" # If a pidfile is still around (for example after a container restart), # delete it so that docker can start. From a467a8a09486e9770a4d7de4f982aeb17d8439b2 Mon Sep 17 00:00:00 2001 From: Ben Firshman Date: Thu, 9 Apr 2015 14:44:07 +0100 Subject: [PATCH 05/19] Merge pull request #1261 from aanand/fix-vars-in-volume-paths Fix vars in volume paths (cherry picked from commit 4926f8aef629631032327542a56ae35099807005) Signed-off-by: Aanand Prasad Conflicts: tests/unit/service_test.py --- compose/config.py | 2 ++ compose/service.py | 4 +--- tests/integration/service_test.py | 18 ++++++++++++++++++ tests/unit/config_test.py | 14 ++++++++++++++ tests/unit/service_test.py | 16 ---------------- 5 files changed, 35 insertions(+), 19 deletions(-) diff --git a/compose/config.py b/compose/config.py index 1dc64af2..2dc59d23 100644 --- a/compose/config.py +++ b/compose/config.py @@ -328,6 +328,8 @@ def resolve_host_paths(volumes, working_dir=None): def resolve_host_path(volume, working_dir): container_path, host_path = split_volume(volume) if host_path is not None: + host_path = os.path.expanduser(host_path) + host_path = os.path.expandvars(host_path) return "%s:%s" % (expand_path(working_dir, host_path), container_path) else: return container_path diff --git a/compose/service.py b/compose/service.py index 936e3f9d..86427a1e 100644 --- a/compose/service.py +++ b/compose/service.py @@ -3,7 +3,6 @@ from __future__ import absolute_import from collections import namedtuple import logging import re -import os from operator import attrgetter import sys import six @@ -586,8 +585,7 @@ def parse_repository_tag(s): def build_volume_binding(volume_spec): internal = {'bind': volume_spec.internal, 'ro': volume_spec.mode == 'ro'} - external = os.path.expanduser(volume_spec.external) - return os.path.abspath(os.path.expandvars(external)), internal + return volume_spec.external, internal def build_port_bindings(ports): diff --git a/tests/integration/service_test.py b/tests/integration/service_test.py index f0fb771d..a89fde97 100644 --- a/tests/integration/service_test.py +++ b/tests/integration/service_test.py @@ -123,6 +123,24 @@ class ServiceTest(DockerClientTestCase): self.assertTrue(path.basename(actual_host_path) == path.basename(host_path), msg=("Last component differs: %s, %s" % (actual_host_path, host_path))) + @mock.patch.dict(os.environ) + def test_create_container_with_home_and_env_var_in_volume_path(self): + os.environ['VOLUME_NAME'] = 'my-volume' + os.environ['HOME'] = '/tmp/home-dir' + expected_host_path = os.path.join(os.environ['HOME'], os.environ['VOLUME_NAME']) + + host_path = '~/${VOLUME_NAME}' + container_path = '/container-path' + + service = self.create_service('db', volumes=['%s:%s' % (host_path, container_path)]) + container = service.create_container() + service.start_container(container) + + actual_host_path = container.get('Volumes')[container_path] + components = actual_host_path.split('/') + self.assertTrue(components[-2:] == ['home-dir', 'my-volume'], + msg="Last two components differ: %s, %s" % (actual_host_path, expected_host_path)) + def test_create_container_with_volumes_from(self): volume_service = self.create_service('data') volume_container_1 = volume_service.create_container() diff --git a/tests/unit/config_test.py b/tests/unit/config_test.py index f25f3a9d..aa14a2a5 100644 --- a/tests/unit/config_test.py +++ b/tests/unit/config_test.py @@ -39,6 +39,20 @@ class ConfigTest(unittest.TestCase): config.make_service_dict('foo', {'ports': ['8000']}) +class VolumePathTest(unittest.TestCase): + @mock.patch.dict(os.environ) + def test_volume_binding_with_environ(self): + os.environ['VOLUME_PATH'] = '/host/path' + d = config.make_service_dict('foo', {'volumes': ['${VOLUME_PATH}:/container/path']}, working_dir='.') + self.assertEqual(d['volumes'], ['/host/path:/container/path']) + + @mock.patch.dict(os.environ) + def test_volume_binding_with_home(self): + os.environ['HOME'] = '/home/user' + d = config.make_service_dict('foo', {'volumes': ['~:/container/path']}, working_dir='.') + self.assertEqual(d['volumes'], ['/home/user:/container/path']) + + class MergeVolumesTest(unittest.TestCase): def test_empty(self): service_dict = config.merge_service_dicts({}, {}) diff --git a/tests/unit/service_test.py b/tests/unit/service_test.py index c70c30bf..24222dfe 100644 --- a/tests/unit/service_test.py +++ b/tests/unit/service_test.py @@ -1,6 +1,5 @@ from __future__ import unicode_literals from __future__ import absolute_import -import os from .. import unittest import mock @@ -301,18 +300,3 @@ class ServiceVolumesTest(unittest.TestCase): self.assertEqual( binding, ('/outside', dict(bind='/inside', ro=False))) - - @mock.patch.dict(os.environ) - def test_build_volume_binding_with_environ(self): - os.environ['VOLUME_PATH'] = '/opt' - binding = build_volume_binding(parse_volume_spec('${VOLUME_PATH}:/opt')) - self.assertEqual(binding, ('/opt', dict(bind='/opt', ro=False))) - - @mock.patch.dict(os.environ) - def test_building_volume_binding_with_home(self): - os.environ['HOME'] = '/home/user' - binding = build_volume_binding(parse_volume_spec('~:/home/user')) - self.assertEqual( - binding, - ('/home/user', dict(bind='/home/user', ro=False))) - From b6acb3cd8cec598504a4f25a2f91383e71d61701 Mon Sep 17 00:00:00 2001 From: Daniel Nephin Date: Tue, 14 Apr 2015 11:04:03 -0400 Subject: [PATCH 06/19] Merge pull request #1278 from albers/completion-run-user Add bash completion for docker-compose run --user (cherry picked from commit 3cd116b99d71f0e0da84e77797392e12070734e1) Signed-off-by: Aanand Prasad --- contrib/completion/bash/docker-compose | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contrib/completion/bash/docker-compose b/contrib/completion/bash/docker-compose index af336803..548773d6 100644 --- a/contrib/completion/bash/docker-compose +++ b/contrib/completion/bash/docker-compose @@ -232,14 +232,14 @@ _docker-compose_run() { compopt -o nospace return ;; - --entrypoint) + --entrypoint|--user|-u) return ;; esac case "$cur" in -*) - COMPREPLY=( $( compgen -W "--allow-insecure-ssl -d --entrypoint -e --no-deps --rm --service-ports -T" -- "$cur" ) ) + COMPREPLY=( $( compgen -W "--allow-insecure-ssl -d --entrypoint -e --no-deps --rm --service-ports -T --user -u" -- "$cur" ) ) ;; *) __docker-compose_services_all From 39ae91c81c2dd8cddd2cbb3601dee8349a596340 Mon Sep 17 00:00:00 2001 From: Aanand Prasad Date: Mon, 23 Mar 2015 10:40:23 -0700 Subject: [PATCH 07/19] Bump 1.2.0 Signed-off-by: Aanand Prasad --- CHANGES.md | 23 +++++++++++++++++++++++ compose/__init__.py | 2 +- docs/completion.md | 2 +- docs/install.md | 2 +- 4 files changed, 26 insertions(+), 3 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 75c13090..277a188a 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,29 @@ Change log ========== +1.2.0 (2015-04-16) +------------------ + +- `docker-compose.yml` now supports an `extends` option, which enables a service to inherit configuration from another service in another configuration file. This is really good for sharing common configuration between apps, or for configuring the same app for different environments. Here's the [documentation](https://github.com/docker/compose/blob/master/docs/yml.md#extends). + +- When using Compose with a Swarm cluster, containers that depend on one another will be co-scheduled on the same node. This means that most Compose apps will now work out of the box, as long as they don't use `build`. + +- Repeated invocations of `docker-compose up` when using Compose with a Swarm cluster now work reliably. + +- Directories passed to `build`, filenames passed to `env_file` and volume host paths passed to `volumes` are now treated as relative to the *directory of the configuration file*, not the directory that `docker-compose` is being run in. In the majority of cases, those are the same, but if you use the `-f|--file` argument to specify a configuration file in another directory, **this is a breaking change**. + +- A service can now share another service's network namespace with `net: container:`. + +- `volumes_from` and `net: container:` entries are taken into account when resolving dependencies, so `docker-compose up ` will correctly start all dependencies of ``. + +- `docker-compose run` now accepts a `--user` argument to specify a user to run the command as, just like `docker run`. + +- The `up`, `stop` and `restart` commands now accept a `--timeout` (or `-t`) argument to specify how long to wait when attempting to gracefully stop containers, just like `docker stop`. + +- `docker-compose rm` now accepts `-f` as a shorthand for `--force`, just like `docker rm`. + +Thanks, @abesto, @albers, @alunduil, @dnephin, @funkyfuture, @gilclark, @IanVS, @KingsleyKelly, @knutwalker, @thaJeztah and @vmalloc! + 1.1.0 (2015-02-25) ------------------ diff --git a/compose/__init__.py b/compose/__init__.py index c770b395..2c426c78 100644 --- a/compose/__init__.py +++ b/compose/__init__.py @@ -1,4 +1,4 @@ from __future__ import unicode_literals from .service import Service # noqa:flake8 -__version__ = '1.1.0' +__version__ = '1.2.0' diff --git a/docs/completion.md b/docs/completion.md index d9b94f6c..6ac95c2e 100644 --- a/docs/completion.md +++ b/docs/completion.md @@ -17,7 +17,7 @@ On a Mac, install with `brew install bash-completion` Place the completion script in `/etc/bash_completion.d/` (`/usr/local/etc/bash_completion.d/` on a Mac), using e.g. - curl -L https://raw.githubusercontent.com/docker/compose/1.1.0/contrib/completion/bash/docker-compose > /etc/bash_completion.d/docker-compose + curl -L https://raw.githubusercontent.com/docker/compose/1.2.0/contrib/completion/bash/docker-compose > /etc/bash_completion.d/docker-compose Completion will be available upon next login. diff --git a/docs/install.md b/docs/install.md index 0e60e1f1..064ddc5f 100644 --- a/docs/install.md +++ b/docs/install.md @@ -20,7 +20,7 @@ First, install Docker version 1.3 or greater: To install Compose, run the following commands: - curl -L https://github.com/docker/compose/releases/download/1.1.0/docker-compose-`uname -s`-`uname -m` > /usr/local/bin/docker-compose + curl -L https://github.com/docker/compose/releases/download/1.2.0/docker-compose-`uname -s`-`uname -m` > /usr/local/bin/docker-compose chmod +x /usr/local/bin/docker-compose Optionally, you can also install [command completion](completion.md) for the From 43af1684c143ad8d07a5132fb376d4812497a6d3 Mon Sep 17 00:00:00 2001 From: Aanand Prasad Date: Fri, 17 Apr 2015 16:02:57 +0100 Subject: [PATCH 08/19] Update docs for 1.2.0 Signed-off-by: Aanand Prasad --- docs/extends.md | 364 +++++++++++++++++++++++++++++++++++++++++++++ docs/index.md | 30 ++++ docs/install.md | 6 +- docs/mkdocs.yml | 2 + docs/production.md | 77 ++++++++++ docs/yml.md | 54 +++---- 6 files changed, 495 insertions(+), 38 deletions(-) create mode 100644 docs/extends.md create mode 100644 docs/production.md diff --git a/docs/extends.md b/docs/extends.md new file mode 100644 index 00000000..2393ca6a --- /dev/null +++ b/docs/extends.md @@ -0,0 +1,364 @@ +page_title: Extending services in Compose +page_description: How to use Docker Compose's "extends" keyword to share configuration between files and projects +page_keywords: fig, composition, compose, docker, orchestration, documentation, docs + + +## Extending services in Compose + +Docker Compose's `extends` keyword enables sharing of common configurations +among different files, or even different projects entirely. Extending services +is useful if you have several applications that reuse commonly-defined services. +Using `extends` you can define a service in one place and refer to it from +anywhere. + +Alternatively, you can deploy the same application to multiple environments with +a slightly different set of services in each case (or with changes to the +configuration of some services). Moreover, you can do so without copy-pasting +the configuration around. + +### Understand the extends configuration + +When defining any service in `docker-compose.yml`, you can declare that you are +extending another service like this: + +```yaml +web: + extends: + file: common-services.yml + service: webapp +``` + +This instructs Compose to re-use the configuration for the `webapp` service +defined in the `common-services.yml` file. Suppose that `common-services.yml` +looks like this: + +```yaml +webapp: + build: . + ports: + - "8000:8000" + volumes: + - "/data" +``` + +In this case, you'll get exactly the same result as if you wrote +`docker-compose.yml` with that `build`, `ports` and `volumes` configuration +defined directly under `web`. + +You can go further and define (or re-define) configuration locally in +`docker-compose.yml`: + +```yaml +web: + extends: + file: common-services.yml + service: webapp + environment: + - DEBUG=1 + cpu_shares: 5 +``` + +You can also write other services and link your `web` service to them: + +```yaml +web: + extends: + file: common-services.yml + service: webapp + environment: + - DEBUG=1 + cpu_shares: 5 + links: + - db +db: + image: postgres +``` + +For full details on how to use `extends`, refer to the [reference](#reference). + +### Example use case + +In this example, you’ll repurpose the example app from the [quick start +guide](index.md). (If you're not familiar with Compose, it's recommended that +you go through the quick start first.) This example assumes you want to use +Compose both to develop an application locally and then deploy it to a +production environment. + +The local and production environments are similar, but there are some +differences. In development, you mount the application code as a volume so that +it can pick up changes; in production, the code should be immutable from the +outside. This ensures it’s not accidentally changed. The development environment +uses a local Redis container, but in production another team manages the Redis +service, which is listening at `redis-production.example.com`. + +To configure with `extends` for this sample, you must: + +1. Define the web application as a Docker image in `Dockerfile` and a Compose + service in `common.yml`. + +2. Define the development environment in the standard Compose file, + `docker-compose.yml`. + + - Use `extends` to pull in the web service. + - Configure a volume to enable code reloading. + - Create an additional Redis service for the application to use locally. + +3. Define the production environment in a third Compose file, `production.yml`. + + - Use `extends` to pull in the web service. + - Configure the web service to talk to the external, production Redis service. + +#### Define the web app + +Defining the web application requires the following: + +1. Create an `app.py` file. + + This file contains a simple Python application that uses Flask to serve HTTP + and increments a counter in Redis: + + from flask import Flask + from redis import Redis + import os + + app = Flask(__name__) + redis = Redis(host=os.environ['REDIS_HOST'], port=6379) + + @app.route('/') + def hello(): + redis.incr('hits') + return 'Hello World! I have been seen %s times.\n' % redis.get('hits') + + if __name__ == "__main__": + app.run(host="0.0.0.0", debug=True) + + This code uses a `REDIS_HOST` environment variable to determine where to + find Redis. + +2. Define the Python dependencies in a `requirements.txt` file: + + flask + redis + +3. Create a `Dockerfile` to build an image containing the app: + + FROM python:2.7 + ADD . /code + WORKDIR /code + RUN pip install -r + requirements.txt + CMD python app.py + +4. Create a Compose configuration file called `common.yml`: + + This configuration defines how to run the app. + + web: + build: . + ports: + - "5000:5000" + + Typically, you would have dropped this configuration into + `docker-compose.yml` file, but in order to pull it into multiple files with + `extends`, it needs to be in a separate file. + +#### Define the development environment + +1. Create a `docker-compose.yml` file. + + The `extends` option pulls in the `web` service from the `common.yml` file + you created in the previous section. + + web: + extends: + file: common.yml + service: web + volumes: + - .:/code + links: + - redis + environment: + - REDIS_HOST=redis + redis: + image: redis + + The new addition defines a `web` service that: + + - Fetches the base configuration for `web` out of `common.yml`. + - Adds `volumes` and `links` configuration to the base (`common.yml`) + configuration. + - Sets the `REDIS_HOST` environment variable to point to the linked redis + container. This environment uses a stock `redis` image from the Docker Hub. + +2. Run `docker-compose up`. + + Compose creates, links, and starts a web and redis container linked together. + It mounts your application code inside the web container. + +3. Verify that the code is mounted by changing the message in + `app.py`—say, from `Hello world!` to `Hello from Compose!`. + + Don't forget to refresh your browser to see the change! + +#### Define the production environment + +You are almost done. Now, define your production environment: + +1. Create a `production.yml` file. + + As with `docker-compose.yml`, the `extends` option pulls in the `web` service + from `common.yml`. + + web: + extends: + file: common.yml + service: web + environment: + - REDIS_HOST=redis-production.example.com + +2. Run `docker-compose -f production.yml up`. + + Compose creates *just* a web container and configures the Redis connection via + the `REDIS_HOST` environment variable. This variable points to the production + Redis instance. + + > **Note**: If you try to load up the webapp in your browser you'll get an + > error—`redis-production.example.com` isn't actually a Redis server. + +You've now done a basic `extends` configuration. As your application develops, +you can make any necessary changes to the web service in `common.yml`. Compose +picks up both the development and production environments when you next run +`docker-compose`. You don't have to do any copy-and-paste, and you don't have to +manually keep both environments in sync. + + +### Reference + +You can use `extends` on any service together with other configuration keys. It +always expects a dictionary that should always contain two keys: `file` and +`service`. + +The `file` key specifies which file to look in. It can be an absolute path or a +relative one—if relative, it's treated as relative to the current file. + +The `service` key specifies the name of the service to extend, for example `web` +or `database`. + +You can extend a service that itself extends another. You can extend +indefinitely. Compose does not support circular references and `docker-compose` +returns an error if it encounters them. + +#### Adding and overriding configuration + +Compose copies configurations from the original service over to the local one, +**except** for `links` and `volumes_from`. These exceptions exist to avoid +implicit dependencies—you always define `links` and `volumes_from` +locally. This ensures dependencies between services are clearly visible when +reading the current file. Defining these locally also ensures changes to the +referenced file don't result in breakage. + +If a configuration option is defined in both the original service and the local +service, the local value either *override*s or *extend*s the definition of the +original service. This works differently for other configuration options. + +For single-value options like `image`, `command` or `mem_limit`, the new value +replaces the old value. **This is the default behaviour - all exceptions are +listed below.** + +```yaml +# original service +command: python app.py + +# local service +command: python otherapp.py + +# result +command: python otherapp.py +``` + +In the case of `build` and `image`, using one in the local service causes +Compose to discard the other, if it was defined in the original service. + +```yaml +# original service +build: . + +# local service +image: redis + +# result +image: redis +``` + +```yaml +# original service +image: redis + +# local service +build: . + +# result +build: . +``` + +For the **multi-value options** `ports`, `expose`, `external_links`, `dns` and +`dns_search`, Compose concatenates both sets of values: + +```yaml +# original service +expose: + - "3000" + +# local service +expose: + - "4000" + - "5000" + +# result +expose: + - "3000" + - "4000" + - "5000" +``` + +In the case of `environment`, Compose "merges" entries together with +locally-defined values taking precedence: + +```yaml +# original service +environment: + - FOO=original + - BAR=original + +# local service +environment: + - BAR=local + - BAZ=local + +# result +environment: + - FOO=original + - BAR=local + - BAZ=local +``` + +Finally, for `volumes`, Compose "merges" entries together with locally-defined +bindings taking precedence: + +```yaml +# original service +volumes: + - /original-dir/foo:/foo + - /original-dir/bar:/bar + +# local service +volumes: + - /local-dir/bar:/bar + - /local-dir/baz/:baz + +# result +volumes: + - /original-dir/foo:/foo + - /local-dir/bar:/bar + - /local-dir/baz/:baz +``` \ No newline at end of file diff --git a/docs/index.md b/docs/index.md index a75e7285..78d9de28 100644 --- a/docs/index.md +++ b/docs/index.md @@ -5,6 +5,8 @@ page_keywords: documentation, docs, docker, compose, orchestration, containers # Docker Compose +## Overview + Compose is a tool for defining and running complex applications with Docker. With Compose, you define a multi-container application in a single file, then spin your application up in a single command which does everything that needs to @@ -191,3 +193,31 @@ At this point, you have seen the basics of how Compose works. [Rails](rails.md), or [Wordpress](wordpress.md). - See the reference guides for complete details on the [commands](cli.md), the [configuration file](yml.md) and [environment variables](env.md). + +## Release Notes + +### Version 1.2.0 (April 7, 2015) + +For complete information on this release, see the [1.2.0 Milestone project page](https://github.com/docker/compose/wiki/1.2.0-Milestone-Project-Page). +In addition to bug fixes and refinements, this release adds the following: + +* The `extends` keyword, which adds the ability to extend services by sharing common configurations. For details, see +[PR #972](https://github.com/docker/compose/pull/1088). + +* Better integration with Swarm. Swarm will now schedule inter-dependent +containers on the same host. For details, see +[PR #972](https://github.com/docker/compose/pull/972). + +## Getting help + +Docker Compose is still in its infancy and under active development. If you need +help, would like to contribute, or simply want to talk about the project with +like-minded individuals, we have a number of open channels for communication. + +* To report bugs or file feature requests: please use the [issue tracker on Github](https://github.com/docker/compose/issues). + +* To talk about the project with people in real time: please join the `#docker-compose` channel on IRC. + +* To contribute code or documentation changes: please submit a [pull request on Github](https://github.com/docker/compose/pulls). + +For more information and resources, please visit the [Getting Help project page](https://docs.docker.com/project/get-help/). diff --git a/docs/install.md b/docs/install.md index 064ddc5f..24928d74 100644 --- a/docs/install.md +++ b/docs/install.md @@ -1,5 +1,5 @@ page_title: Installing Compose -page_description: How to intall Docker Compose +page_description: How to install Docker Compose page_keywords: compose, orchestration, install, installation, docker, documentation @@ -23,6 +23,8 @@ To install Compose, run the following commands: curl -L https://github.com/docker/compose/releases/download/1.2.0/docker-compose-`uname -s`-`uname -m` > /usr/local/bin/docker-compose chmod +x /usr/local/bin/docker-compose +> Note: If you get a "Permission denied" error, your `/usr/local/bin` directory probably isn't writable and you'll need to install Compose as the superuser. Run `sudo -i`, then the two commands above, then `exit`. + Optionally, you can also install [command completion](completion.md) for the bash shell. @@ -31,7 +33,7 @@ Compose can also be installed as a Python package: $ sudo pip install -U docker-compose -No further steps are required; Compose should now be successfully installed. +No further steps are required; Compose should now be successfully installed. You can test the installation by running `docker-compose --version`. ## Compose documentation diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index 14335873..428439bc 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -1,5 +1,7 @@ - ['compose/index.md', 'User Guide', 'Docker Compose' ] +- ['compose/production.md', 'User Guide', 'Using Compose in production' ] +- ['compose/extends.md', 'User Guide', 'Extending services in Compose'] - ['compose/install.md', 'Installation', 'Docker Compose'] - ['compose/cli.md', 'Reference', 'Compose command line'] - ['compose/yml.md', 'Reference', 'Compose yml'] diff --git a/docs/production.md b/docs/production.md new file mode 100644 index 00000000..8524c99b --- /dev/null +++ b/docs/production.md @@ -0,0 +1,77 @@ +page_title: Using Compose in production +page_description: Guide to using Docker Compose in production +page_keywords: documentation, docs, docker, compose, orchestration, containers, production + + +## Using Compose in production + +While **Compose is not yet considered production-ready**, if you'd like to experiment and learn more about using it in production deployments, this guide +can help. +The project is actively working towards becoming +production-ready; to learn more about the progress being made, check out the +[roadmap](https://github.com/docker/compose/blob/master/ROADMAP.md) for details +on how it's coming along and what still needs to be done. + +When deploying to production, you'll almost certainly want to make changes to +your app configuration that are more appropriate to a live environment. These +changes may include: + +- Removing any volume bindings for application code, so that code stays inside + the container and can't be changed from outside +- Binding to different ports on the host +- Setting environment variables differently (e.g., to decrease the verbosity of + logging, or to enable email sending) +- Specifying a restart policy (e.g., `restart: always`) to avoid downtime +- Adding extra services (e.g., a log aggregator) + +For this reason, you'll probably want to define a separate Compose file, say +`production.yml`, which specifies production-appropriate configuration. + +> **Note:** The [extends](extends.md) keyword is useful for maintaining multiple +> Compose files which re-use common services without having to manually copy and +> paste. + +Once you've got an alternate configuration file, make Compose use it +by setting the `COMPOSE_FILE` environment variable: + + $ COMPOSE_FILE=production.yml + $ docker-compose up -d + +> **Note:** You can also use the file for a one-off command without setting +> an environment variable. You do this by passing the `-f` flag, e.g., +> `docker-compose -f production.yml up -d`. + +### Deploying changes + +When you make changes to your app code, you'll need to rebuild your image and +recreate your app's containers. To redeploy a service called +`web`, you would use: + + $ docker-compose build web + $ docker-compose up --no-deps -d web + +This will first rebuild the image for `web` and then stop, destroy, and recreate +*just* the `web` service. The `--no-deps` flag prevents Compose from also +recreating any services which `web` depends on. + +### Running Compose on a single server + +You can use Compose to deploy an app to a remote Docker host by setting the +`DOCKER_HOST`, `DOCKER_TLS_VERIFY`, and `DOCKER_CERT_PATH` environment variables +appropriately. For tasks like this, +[Docker Machine](https://docs.docker.com/machine) makes managing local and +remote Docker hosts very easy, and is recommended even if you're not deploying +remotely. + +Once you've set up your environment variables, all the normal `docker-compose` +commands will work with no further configuration. + +### Running Compose on a Swarm cluster + +[Docker Swarm](https://docs.docker.com/swarm), a Docker-native clustering +system, exposes the same API as a single Docker host, which means you can use +Compose against a Swarm instance and run your apps across multiple hosts. + +Compose/Swarm integration is still in the experimental stage, and Swarm is still +in beta, but if you'd like to explore and experiment, check out the +[integration guide](https://github.com/docker/compose/blob/master/SWARM.md). diff --git a/docs/yml.md b/docs/yml.md index a9909e81..c375648d 100644 --- a/docs/yml.md +++ b/docs/yml.md @@ -173,8 +173,12 @@ env_file: - /opt/secrets.env ``` +Compose expects each line in an env file to be in `VAR=VAL` format. Lines +beginning with `#` (i.e. comments) are ignored, as are blank lines. + ``` -RACK_ENV: development +# Set Rails/Rack environment +RACK_ENV=development ``` ### extends @@ -217,42 +221,10 @@ Here, the `web` service in **development.yml** inherits the configuration of the `webapp` service in **common.yml** - the `build` and `environment` keys - and adds `ports` and `links` configuration. It overrides one of the defined environment variables (DEBUG) with a new value, and the other one -(SEND_EMAILS) is left untouched. It's exactly as if you defined `web` like -this: +(SEND_EMAILS) is left untouched. -```yaml -web: - build: ./webapp - ports: - - "8000:8000" - links: - - db - environment: - - DEBUG=true - - SEND_EMAILS=false -``` - -The `extends` option is great for sharing configuration between different -apps, or for configuring the same app differently for different environments. -You could write a new file for a staging environment, **staging.yml**, which -binds to a different port and doesn't turn on debugging: - -``` -web: - extends: - file: common.yml - service: webapp - ports: - - "80:8000" - links: - - db -db: - image: postgres -``` - -> **Note:** When you extend a service, `links` and `volumes_from` -> configuration options are **not** inherited - you will have to define -> those manually each time you extend it. +For more on `extends`, see the [tutorial](extends.md#example) and +[reference](extends.md#reference). ### net @@ -264,6 +236,16 @@ net: "none" net: "container:[name or id]" net: "host" ``` +### pid + +``` +pid: "host" +``` + +Sets the PID mode to the host PID mode. This turns on sharing between +container and the host operating system the PID address space. Containers +launched with this flag will be able to access and manipulate other +containers in the bare-metal machine's namespace and vise-versa. ### dns From 686c25d50ff3822e0f1515cf6aa0c13de97a4368 Mon Sep 17 00:00:00 2001 From: Aanand Prasad Date: Wed, 27 May 2015 15:13:12 +0100 Subject: [PATCH 09/19] Script to prepare OSX build environment Signed-off-by: Aanand Prasad --- CONTRIBUTING.md | 12 ++++++++---- script/build-osx | 2 +- script/prepare-osx | 22 ++++++++++++++++++++++ 3 files changed, 31 insertions(+), 5 deletions(-) create mode 100755 script/prepare-osx diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 373c8dc6..fddf888d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -53,16 +53,20 @@ you can specify a test directory, file, module, class or method: ## Building binaries -Linux: +`script/build-linux` will build the Linux binary inside a Docker container: $ script/build-linux -OS X: +`script/build-osx` will build the Mac OS X binary inside a virtualenv: $ script/build-osx -Note that this only works on Mountain Lion, not Mavericks, due to a -[bug in PyInstaller](http://www.pyinstaller.org/ticket/807). +For official releases, you should build inside a Mountain Lion VM for proper +compatibility. Run the this script first to prepare the environment before +building - it will use Homebrew to make sure Python is installed and +up-to-date. + + $ script/prepare-osx ## Release process diff --git a/script/build-osx b/script/build-osx index 26309744..6ad00bcd 100755 --- a/script/build-osx +++ b/script/build-osx @@ -1,7 +1,7 @@ #!/bin/bash set -ex rm -rf venv -virtualenv venv +virtualenv -p /usr/local/bin/python venv venv/bin/pip install -r requirements.txt venv/bin/pip install -r requirements-dev.txt venv/bin/pip install . diff --git a/script/prepare-osx b/script/prepare-osx new file mode 100755 index 00000000..69ac56f1 --- /dev/null +++ b/script/prepare-osx @@ -0,0 +1,22 @@ +#!/bin/bash + +set -ex + +if !(which brew); then + ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" +fi + +brew update + +if [ ! -f /usr/local/bin/python ]; then + brew install python +fi + +if [ -n "$(brew outdated | grep python)" ]; then + brew upgrade python +fi + +if !(which virtualenv); then + pip install virtualenv +fi + From 93a846db318bbf7e332db39f0ed7a764053948d6 Mon Sep 17 00:00:00 2001 From: Aanand Prasad Date: Thu, 28 May 2015 17:18:04 +0100 Subject: [PATCH 10/19] Report Python and OpenSSL versions in --version output Signed-off-by: Aanand Prasad Conflicts: compose/cli/utils.py --- compose/cli/main.py | 5 ++--- compose/cli/utils.py | 11 +++++++++++ 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/compose/cli/main.py b/compose/cli/main.py index a558e835..61f3ec3f 100644 --- a/compose/cli/main.py +++ b/compose/cli/main.py @@ -10,7 +10,6 @@ import sys from docker.errors import APIError import dockerpty -from .. import __version__ from .. import legacy from ..project import NoSuchService, ConfigurationError from ..service import BuildError, CannotBeScaledError, NeedsBuildError @@ -20,7 +19,7 @@ from .docopt_command import NoSuchCommand from .errors import UserError from .formatter import Formatter from .log_printer import LogPrinter -from .utils import yesno +from .utils import get_version_info, yesno log = logging.getLogger(__name__) @@ -104,7 +103,7 @@ class TopLevelCommand(Command): """ def docopt_options(self): options = super(TopLevelCommand, self).docopt_options() - options['version'] = "docker-compose %s" % __version__ + options['version'] = get_version_info() return options def build(self, project, options): diff --git a/compose/cli/utils.py b/compose/cli/utils.py index 5f5fed64..93b99103 100644 --- a/compose/cli/utils.py +++ b/compose/cli/utils.py @@ -5,6 +5,9 @@ import datetime import os import subprocess import platform +import ssl + +from .. import __version__ def yesno(prompt, default=None): @@ -120,3 +123,11 @@ def is_mac(): def is_ubuntu(): return platform.system() == 'Linux' and platform.linux_distribution()[0] == 'Ubuntu' + + +def get_version_info(): + return '\n'.join([ + 'docker-compose version: %s' % __version__, + "%s version: %s" % (platform.python_implementation(), platform.python_version()), + "OpenSSL version: %s" % ssl.OPENSSL_VERSION, + ]) From f3d0c63db2621a7bbe77164a23d11d3530bd5d19 Mon Sep 17 00:00:00 2001 From: Aanand Prasad Date: Thu, 28 May 2015 17:24:03 +0100 Subject: [PATCH 11/19] Make sure we use Python 2.7.9 and OpenSSL 1.0.1 when building OSX binary Signed-off-by: Aanand Prasad --- script/build-osx | 3 +++ script/prepare-osx | 39 +++++++++++++++++++++++++++++++++++---- 2 files changed, 38 insertions(+), 4 deletions(-) diff --git a/script/build-osx b/script/build-osx index 6ad00bcd..d6561aee 100755 --- a/script/build-osx +++ b/script/build-osx @@ -1,5 +1,8 @@ #!/bin/bash set -ex + +PATH="/usr/local/bin:$PATH" + rm -rf venv virtualenv -p /usr/local/bin/python venv venv/bin/pip install -r requirements.txt diff --git a/script/prepare-osx b/script/prepare-osx index 69ac56f1..ca2776b6 100755 --- a/script/prepare-osx +++ b/script/prepare-osx @@ -2,20 +2,51 @@ set -ex +python_version() { + python -V 2>&1 +} + +openssl_version() { + python -c "import ssl; print ssl.OPENSSL_VERSION" +} + +desired_python_version="2.7.9" +desired_python_brew_version="2.7.9" +python_formula="https://raw.githubusercontent.com/Homebrew/homebrew/1681e193e4d91c9620c4901efd4458d9b6fcda8e/Library/Formula/python.rb" + +desired_openssl_version="1.0.1j" +desired_openssl_brew_version="1.0.1j_1" +openssl_formula="https://raw.githubusercontent.com/Homebrew/homebrew/62fc2a1a65e83ba9dbb30b2e0a2b7355831c714b/Library/Formula/openssl.rb" + +PATH="/usr/local/bin:$PATH" + if !(which brew); then ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" fi brew update -if [ ! -f /usr/local/bin/python ]; then - brew install python +if !(python_version | grep "$desired_python_version"); then + if brew list | grep python; then + brew unlink python + fi + + brew install "$python_formula" + brew switch python "$desired_python_brew_version" fi -if [ -n "$(brew outdated | grep python)" ]; then - brew upgrade python +if !(openssl_version | grep "$desired_openssl_version"); then + if brew list | grep openssl; then + brew unlink openssl + fi + + brew install "$openssl_formula" + brew switch openssl "$desired_openssl_brew_version" fi +echo "*** Using $(python_version)" +echo "*** Using $(openssl_version)" + if !(which virtualenv); then pip install virtualenv fi From 8749bc08443ddb344ecf683e796cdb2d814b7f68 Mon Sep 17 00:00:00 2001 From: Aanand Prasad Date: Mon, 1 Jun 2015 14:01:30 +0100 Subject: [PATCH 12/19] Build Python 2.7.9 in Docker image Signed-off-by: Aanand Prasad --- Dockerfile | 39 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 36 insertions(+), 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index b2ae0063..fca5f980 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,9 +3,11 @@ FROM debian:wheezy RUN set -ex; \ apt-get update -qq; \ apt-get install -y \ - python \ - python-pip \ - python-dev \ + gcc \ + make \ + zlib1g \ + zlib1g-dev \ + libssl-dev \ git \ apt-transport-https \ ca-certificates \ @@ -15,6 +17,37 @@ RUN set -ex; \ ; \ rm -rf /var/lib/apt/lists/* +# Build Python 2.7.9 from source +RUN set -ex; \ + curl -LO https://www.python.org/ftp/python/2.7.9/Python-2.7.9.tgz; \ + tar -xzf Python-2.7.9.tgz; \ + cd Python-2.7.9; \ + ./configure --enable-shared; \ + make; \ + make install; \ + cd ..; \ + rm -rf /Python-2.7.9; \ + rm Python-2.7.9.tgz + +# Make libpython findable +ENV LD_LIBRARY_PATH /usr/local/lib + +# Install setuptools +RUN set -ex; \ + curl -LO https://bootstrap.pypa.io/ez_setup.py; \ + python ez_setup.py; \ + rm ez_setup.py + +# Install pip +RUN set -ex; \ + curl -LO https://pypi.python.org/packages/source/p/pip/pip-7.0.1.tar.gz; \ + tar -xzf pip-7.0.1.tar.gz; \ + cd pip-7.0.1; \ + python setup.py install; \ + cd ..; \ + rm -rf pip-7.0.1; \ + rm pip-7.0.1.tar.gz + ENV ALL_DOCKER_VERSIONS 1.6.0 RUN set -ex; \ From 5a5bffebd178670e602e2e9ea8c177bc32ef62b5 Mon Sep 17 00:00:00 2001 From: Aanand Prasad Date: Wed, 27 May 2015 12:49:58 +0100 Subject: [PATCH 13/19] Merge pull request #1464 from twhiteman/bug1461 Possible division by zero error when pulling an image - fixes #1463 (cherry picked from commit d0e87929a1f39b4e98c2c8497f3f0ffc09fb9e43) Signed-off-by: Aanand Prasad --- compose/progress_stream.py | 5 +++-- tests/unit/progress_stream_test.py | 18 ++++++++++++++++++ 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/compose/progress_stream.py b/compose/progress_stream.py index 39aab5ff..317c6e81 100644 --- a/compose/progress_stream.py +++ b/compose/progress_stream.py @@ -74,8 +74,9 @@ def print_output_event(event, stream, is_terminal): stream.write("%s %s%s" % (status, event['progress'], terminator)) elif 'progressDetail' in event: detail = event['progressDetail'] - if 'current' in detail: - percentage = float(detail['current']) / float(detail['total']) * 100 + total = detail.get('total') + if 'current' in detail and total: + percentage = float(detail['current']) / float(total) * 100 stream.write('%s (%.1f%%)%s' % (status, percentage, terminator)) else: stream.write('%s%s' % (status, terminator)) diff --git a/tests/unit/progress_stream_test.py b/tests/unit/progress_stream_test.py index 14256068..317b77e9 100644 --- a/tests/unit/progress_stream_test.py +++ b/tests/unit/progress_stream_test.py @@ -17,3 +17,21 @@ class ProgressStreamTestCase(unittest.TestCase): ] events = progress_stream.stream_output(output, StringIO()) self.assertEqual(len(events), 1) + + def test_stream_output_div_zero(self): + output = [ + '{"status": "Downloading", "progressDetail": {"current": ' + '0, "start": 1413653874, "total": 0}, ' + '"progress": "..."}', + ] + events = progress_stream.stream_output(output, StringIO()) + self.assertEqual(len(events), 1) + + def test_stream_output_null_total(self): + output = [ + '{"status": "Downloading", "progressDetail": {"current": ' + '0, "start": 1413653874, "total": null}, ' + '"progress": "..."}', + ] + events = progress_stream.stream_output(output, StringIO()) + self.assertEqual(len(events), 1) From 4f4ea2a402a42c29c9867b02287dd7ded2d5b0d0 Mon Sep 17 00:00:00 2001 From: Ben Firshman Date: Fri, 29 May 2015 14:45:21 +0100 Subject: [PATCH 14/19] Merge pull request #1325 from sdurrheimer/master Zsh completion for docker-compose (cherry picked from commit b638728d6ca21982e321b4069ef92f8367f069f4) Signed-off-by: Aanand Prasad Conflicts: docs/completion.md --- CONTRIBUTING.md | 1 - contrib/completion/zsh/_docker-compose | 304 +++++++++++++++++++++++++ docs/completion.md | 37 ++- 3 files changed, 333 insertions(+), 9 deletions(-) create mode 100644 contrib/completion/zsh/_docker-compose diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index fddf888d..6914e215 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -73,7 +73,6 @@ up-to-date. 1. Open pull request that: - Updates the version in `compose/__init__.py` - Updates the binary URL in `docs/install.md` - - Updates the script URL in `docs/completion.md` - Adds release notes to `CHANGES.md` 2. Create unpublished GitHub release with release notes 3. Build Linux version on any Docker host with `script/build-linux` and attach diff --git a/contrib/completion/zsh/_docker-compose b/contrib/completion/zsh/_docker-compose new file mode 100644 index 00000000..31052e1e --- /dev/null +++ b/contrib/completion/zsh/_docker-compose @@ -0,0 +1,304 @@ +#compdef docker-compose + +# Description +# ----------- +# zsh completion for docker-compose +# https://github.com/sdurrheimer/docker-compose-zsh-completion +# ------------------------------------------------------------------------- +# Version +# ------- +# 0.1.0 +# ------------------------------------------------------------------------- +# Authors +# ------- +# * Steve Durrheimer +# ------------------------------------------------------------------------- +# Inspiration +# ----------- +# * @albers docker-compose bash completion script +# * @felixr docker zsh completion script : https://github.com/felixr/docker-zsh-completion +# ------------------------------------------------------------------------- + +# For compatibility reasons, Compose and therefore its completion supports several +# stack compositon files as listed here, in descending priority. +# Support for these filenames might be dropped in some future version. +__docker-compose_compose_file() { + local file + for file in docker-compose.y{,a}ml fig.y{,a}ml ; do + [ -e $file ] && { + echo $file + return + } + done + echo docker-compose.yml +} + +# Extracts all service names from docker-compose.yml. +___docker-compose_all_services_in_compose_file() { + local already_selected + local -a services + already_selected=$(echo ${words[@]} | tr " " "|") + awk -F: '/^[a-zA-Z0-9]/{print $1}' "${compose_file:-$(__docker-compose_compose_file)}" 2>/dev/null | grep -Ev "$already_selected" +} + +# All services, even those without an existing container +__docker-compose_services_all() { + services=$(___docker-compose_all_services_in_compose_file) + _alternative "args:services:($services)" +} + +# All services that have an entry with the given key in their docker-compose.yml section +___docker-compose_services_with_key() { + local already_selected + local -a buildable + already_selected=$(echo ${words[@]} | tr " " "|") + # flatten sections to one line, then filter lines containing the key and return section name. + awk '/^[a-zA-Z0-9]/{printf "\n"};{printf $0;next;}' "${compose_file:-$(__docker-compose_compose_file)}" 2>/dev/null | awk -F: -v key=": +$1:" '$0 ~ key {print $1}' 2>/dev/null | grep -Ev "$already_selected" +} + +# All services that are defined by a Dockerfile reference +__docker-compose_services_from_build() { + buildable=$(___docker-compose_services_with_key build) + _alternative "args:buildable services:($buildable)" +} + +# All services that are defined by an image +__docker-compose_services_from_image() { + pullable=$(___docker-compose_services_with_key image) + _alternative "args:pullable services:($pullable)" +} + +__docker-compose_get_services() { + local kind expl + declare -a running stopped lines args services + + docker_status=$(docker ps > /dev/null 2>&1) + if [ $? -ne 0 ]; then + _message "Error! Docker is not running." + return 1 + fi + + kind=$1 + shift + [[ $kind = (stopped|all) ]] && args=($args -a) + + lines=(${(f)"$(_call_program commands docker ps ${args})"}) + services=(${(f)"$(_call_program commands docker-compose 2>/dev/null ${compose_file:+-f $compose_file} ${compose_project:+-p $compose_project} ps -q)"}) + + # Parse header line to find columns + local i=1 j=1 k header=${lines[1]} + declare -A begin end + while (( $j < ${#header} - 1 )) { + i=$(( $j + ${${header[$j,-1]}[(i)[^ ]]} - 1)) + j=$(( $i + ${${header[$i,-1]}[(i) ]} - 1)) + k=$(( $j + ${${header[$j,-1]}[(i)[^ ]]} - 2)) + begin[${header[$i,$(($j-1))]}]=$i + end[${header[$i,$(($j-1))]}]=$k + } + lines=(${lines[2,-1]}) + + # Container ID + local line s name + local -a names + for line in $lines; do + if [[ $services == *"${line[${begin[CONTAINER ID]},${end[CONTAINER ID]}]%% ##}"* ]]; then + names=(${(ps:,:)${${line[${begin[NAMES]},-1]}%% *}}) + for name in $names; do + s="${${name%_*}#*_}:${(l:15:: :::)${${line[${begin[CREATED]},${end[CREATED]}]/ ago/}%% ##}}" + s="$s, ${line[${begin[CONTAINER ID]},${end[CONTAINER ID]}]%% ##}" + s="$s, ${${${line[$begin[IMAGE],$end[IMAGE]]}/:/\\:}%% ##}" + if [[ ${line[${begin[STATUS]},${end[STATUS]}]} = Exit* ]]; then + stopped=($stopped $s) + else + running=($running $s) + fi + done + fi + done + + [[ $kind = (running|all) ]] && _describe -t services-running "running services" running + [[ $kind = (stopped|all) ]] && _describe -t services-stopped "stopped services" stopped +} + +__docker-compose_stoppedservices() { + __docker-compose_get_services stopped "$@" +} + +__docker-compose_runningservices() { + __docker-compose_get_services running "$@" +} + +__docker-compose_services () { + __docker-compose_get_services all "$@" +} + +__docker-compose_caching_policy() { + oldp=( "$1"(Nmh+1) ) # 1 hour + (( $#oldp )) +} + +__docker-compose_commands () { + local cache_policy + + zstyle -s ":completion:${curcontext}:" cache-policy cache_policy + if [[ -z "$cache_policy" ]]; then + zstyle ":completion:${curcontext}:" cache-policy __docker-compose_caching_policy + fi + + if ( [[ ${+_docker_compose_subcommands} -eq 0 ]] || _cache_invalid docker_compose_subcommands) \ + && ! _retrieve_cache docker_compose_subcommands; + then + local -a lines + lines=(${(f)"$(_call_program commands docker-compose 2>&1)"}) + _docker_compose_subcommands=(${${${lines[$((${lines[(i)Commands:]} + 1)),${lines[(I) *]}]}## #}/ ##/:}) + _store_cache docker_compose_subcommands _docker_compose_subcommands + fi + _describe -t docker-compose-commands "docker-compose command" _docker_compose_subcommands +} + +__docker-compose_subcommand () { + local -a _command_args + integer ret=1 + case "$words[1]" in + (build) + _arguments \ + '--no-cache[Do not use cache when building the image]' \ + '*:services:__docker-compose_services_from_build' && ret=0 + ;; + (help) + _arguments ':subcommand:__docker-compose_commands' && ret=0 + ;; + (kill) + _arguments \ + '-s[SIGNAL to send to the container. Default signal is SIGKILL.]:signal:_signals' \ + '*:running services:__docker-compose_runningservices' && ret=0 + ;; + (logs) + _arguments \ + '--no-color[Produce monochrome output.]' \ + '*:services:__docker-compose_services_all' && ret=0 + ;; + (migrate-to-labels) + _arguments \ + '(-):Recreate containers to add labels' && ret=0 + ;; + (port) + _arguments \ + '--protocol=-[tcp or udap (defaults to tcp)]:protocol:(tcp udp)' \ + '--index=-[index of the container if there are mutiple instances of a service (defaults to 1)]:index: ' \ + '1:running services:__docker-compose_runningservices' \ + '2:port:_ports' && ret=0 + ;; + (ps) + _arguments \ + '-q[Only display IDs]' \ + '*:services:__docker-compose_services_all' && ret=0 + ;; + (pull) + _arguments \ + '--allow-insecure-ssl[Allow insecure connections to the docker registry]' \ + '*:services:__docker-compose_services_from_image' && ret=0 + ;; + (rm) + _arguments \ + '(-f --force)'{-f,--force}"[Don't ask to confirm removal]" \ + '-v[Remove volumes associated with containers]' \ + '*:stopped services:__docker-compose_stoppedservices' && ret=0 + ;; + (run) + _arguments \ + '--allow-insecure-ssl[Allow insecure connections to the docker registry]' \ + '-d[Detached mode: Run container in the background, print new container name.]' \ + '--entrypoint[Overwrite the entrypoint of the image.]:entry point: ' \ + '*-e[KEY=VAL Set an environment variable (can be used multiple times)]:environment variable KEY=VAL: ' \ + '(-u --user)'{-u,--user=-}'[Run as specified username or uid]:username or uid:_users' \ + "--no-deps[Don't start linked services.]" \ + '--rm[Remove container after run. Ignored in detached mode.]' \ + "--service-ports[Run command with the service's ports enabled and mapped to the host.]" \ + '-T[Disable pseudo-tty allocation. By default `docker-compose run` allocates a TTY.]' \ + '(-):services:__docker-compose_services' \ + '(-):command: _command_names -e' \ + '*::arguments: _normal' && ret=0 + ;; + (scale) + _arguments '*:running services:__docker-compose_runningservices' && ret=0 + ;; + (start) + _arguments '*:stopped services:__docker-compose_stoppedservices' && ret=0 + ;; + (stop|restart) + _arguments \ + '(-t --timeout)'{-t,--timeout}"[Specify a shutdown timeout in seconds. (default: 10)]:seconds: " \ + '*:running services:__docker-compose_runningservices' && ret=0 + ;; + (up) + _arguments \ + '--allow-insecure-ssl[Allow insecure connections to the docker registry]' \ + '-d[Detached mode: Run containers in the background, print new container names.]' \ + '--no-color[Produce monochrome output.]' \ + "--no-deps[Don't start linked services.]" \ + "--no-recreate[If containers already exist, don't recreate them.]" \ + "--no-build[Don't build an image, even if it's missing]" \ + '(-t --timeout)'{-t,--timeout}"[Specify a shutdown timeout in seconds. (default: 10)]:seconds: " \ + "--x-smart-recreate[Only recreate containers whose configuration or image needs to be updated. (EXPERIMENTAL)]" \ + '*:services:__docker-compose_services_all' && ret=0 + ;; + (*) + _message 'Unknown sub command' + esac + + return ret +} + +_docker-compose () { + # Support for subservices, which allows for `compdef _docker docker-shell=_docker_containers`. + # Based on /usr/share/zsh/functions/Completion/Unix/_git without support for `ret`. + if [[ $service != docker-compose ]]; then + _call_function - _$service + return + fi + + local curcontext="$curcontext" state line ret=1 + typeset -A opt_args + + _arguments -C \ + '(- :)'{-h,--help}'[Get help]' \ + '--verbose[Show more output]' \ + '(- :)'{-v,--version}'[Print version and exit]' \ + '(-f --file)'{-f,--file}'[Specify an alternate docker-compose file (default: docker-compose.yml)]:file:_files -g "*.yml"' \ + '(-p --project-name)'{-p,--project-name}'[Specify an alternate project name (default: directory name)]:project name:' \ + '(-): :->command' \ + '(-)*:: :->option-or-argument' && ret=0 + + local counter=1 + #local compose_file compose_project + while [ $counter -lt ${#words[@]} ]; do + case "${words[$counter]}" in + -f|--file) + (( counter++ )) + compose_file="${words[$counter]}" + ;; + -p|--project-name) + (( counter++ )) + compose_project="${words[$counter]}" + ;; + *) + ;; + esac + (( counter++ )) + done + + case $state in + (command) + __docker-compose_commands && ret=0 + ;; + (option-or-argument) + curcontext=${curcontext%:*:*}:docker-compose-$words[1]: + __docker-compose_subcommand && ret=0 + ;; + esac + + return ret +} + +_docker-compose "$@" diff --git a/docs/completion.md b/docs/completion.md index 35c53b55..5168971f 100644 --- a/docs/completion.md +++ b/docs/completion.md @@ -3,23 +3,44 @@ layout: default title: Command Completion --- -#Command Completion +# Command Completion Compose comes with [command completion](http://en.wikipedia.org/wiki/Command-line_completion) -for the bash shell. +for the bash and zsh shell. -##Installing Command Completion +## Installing Command Completion + +### Bash Make sure bash completion is installed. If you use a current Linux in a non-minimal installation, bash completion should be available. On a Mac, install with `brew install bash-completion` - -Place the completion script in `/etc/bash_completion.d/` (`/usr/local/etc/bash_completion.d/` on a Mac), using e.g. - curl -L https://raw.githubusercontent.com/docker/compose/1.2.0/contrib/completion/bash/docker-compose > /etc/bash_completion.d/docker-compose - +Place the completion script in `/etc/bash_completion.d/` (`/usr/local/etc/bash_completion.d/` on a Mac), using e.g. + + curl -L https://raw.githubusercontent.com/docker/compose/$(docker-compose --version | awk '{print $2}')/contrib/completion/bash/docker-compose > /etc/bash_completion.d/docker-compose + Completion will be available upon next login. -##Available completions +### Zsh + +Place the completion script in your `/path/to/zsh/completion`, using e.g. `~/.zsh/completion/` + + mkdir -p ~/.zsh/completion + curl -L https://raw.githubusercontent.com/docker/compose/$(docker-compose --version | awk '{print $2}')/contrib/completion/zsh/_docker-compose > ~/.zsh/completion/_docker-compose + +Include the directory in your `$fpath`, e.g. by adding in `~/.zshrc` + + fpath=(~/.zsh/completion $fpath) + +Make sure `compinit` is loaded or do it by adding in `~/.zshrc` + + autoload -Uz compinit && compinit -i + +Then reload your shell + + exec $SHELL -l + +## Available completions Depending on what you typed on the command line so far, it will complete From 631f5be02fdc087420989bf820345406e6bc0c7b Mon Sep 17 00:00:00 2001 From: Daniel Nephin Date: Sat, 30 May 2015 09:01:39 -0500 Subject: [PATCH 15/19] Merge pull request #1481 from albers/completion-smart-recreate Support --x-smart-recreate in bash completion (cherry picked from commit 9a0bb325f2d1203b7aac915c3bfca4347cc93489) Signed-off-by: Aanand Prasad --- contrib/completion/bash/docker-compose | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contrib/completion/bash/docker-compose b/contrib/completion/bash/docker-compose index e62b1d8f..ba3dff35 100644 --- a/contrib/completion/bash/docker-compose +++ b/contrib/completion/bash/docker-compose @@ -104,7 +104,7 @@ _docker-compose_docker-compose() { case "$cur" in -*) - COMPREPLY=( $( compgen -W "--help -h --verbose --version --file -f --project-name -p" -- "$cur" ) ) + COMPREPLY=( $( compgen -W "--help -h --verbose --version -v --file -f --project-name -p" -- "$cur" ) ) ;; *) COMPREPLY=( $( compgen -W "${commands[*]}" -- "$cur" ) ) @@ -293,7 +293,7 @@ _docker-compose_up() { case "$cur" in -*) - COMPREPLY=( $( compgen -W "--allow-insecure-ssl -d --no-build --no-color --no-deps --no-recreate -t --timeout" -- "$cur" ) ) + COMPREPLY=( $( compgen -W "--allow-insecure-ssl -d --no-build --no-color --no-deps --no-recreate -t --timeout --x-smart-recreate" -- "$cur" ) ) ;; *) __docker-compose_services_all From 8ed7dfef6fb8d3f6eeeb4c515315e9ae43baee29 Mon Sep 17 00:00:00 2001 From: Daniel Nephin Date: Mon, 8 Jun 2015 12:48:46 -0400 Subject: [PATCH 16/19] Merge pull request #1525 from aanand/fix-duplicate-logging Fix duplicate logging on up/run (cherry picked from commit e2b790f7328482591863e496de14c825fd3f8a23) Signed-off-by: Aanand Prasad --- compose/cli/main.py | 1 + compose/service.py | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/compose/cli/main.py b/compose/cli/main.py index 61f3ec3f..fa401316 100644 --- a/compose/cli/main.py +++ b/compose/cli/main.py @@ -335,6 +335,7 @@ class TopLevelCommand(Command): container_options['ports'] = [] container = service.create_container( + quiet=True, one_off=True, insecure_registry=insecure_registry, **container_options diff --git a/compose/service.py b/compose/service.py index ccfb3851..dd931bee 100644 --- a/compose/service.py +++ b/compose/service.py @@ -199,6 +199,7 @@ class Service(object): do_build=True, previous_container=None, number=None, + quiet=False, **override_options): """ Create a container for this service. If the image doesn't exist, attempt to pull @@ -216,7 +217,7 @@ class Service(object): previous_container=previous_container, ) - if 'name' in container_options: + if 'name' in container_options and not quiet: log.info("Creating %s..." % container_options['name']) return Container.create(self.client, **container_options) @@ -378,6 +379,7 @@ class Service(object): do_build=False, previous_container=container, number=container.labels.get(LABEL_CONTAINER_NUMBER), + quiet=True, ) self.start_container(new_container) container.remove() From dca3bbdea3eb9991d804cc8b9ac9de34a367b866 Mon Sep 17 00:00:00 2001 From: Daniel Nephin Date: Mon, 8 Jun 2015 16:21:02 -0400 Subject: [PATCH 17/19] Merge pull request #1527 from aanand/remove-logging-on-run-rm Remove logging on run --rm (cherry picked from commit 5578ccbb0113e285a20aeeee820c03766ef1ae6e) Signed-off-by: Aanand Prasad --- compose/cli/main.py | 1 - 1 file changed, 1 deletion(-) diff --git a/compose/cli/main.py b/compose/cli/main.py index fa401316..7fde4eba 100644 --- a/compose/cli/main.py +++ b/compose/cli/main.py @@ -348,7 +348,6 @@ class TopLevelCommand(Command): dockerpty.start(project.client, container.id, interactive=not options['-T']) exit_code = container.wait() if options['--rm']: - log.info("Removing %s..." % container.name) project.client.remove_container(container.id) sys.exit(exit_code) From 8212f1bd45ab36d41895fbbd45490cbb68170187 Mon Sep 17 00:00:00 2001 From: Daniel Nephin Date: Tue, 9 Jun 2015 18:21:14 -0400 Subject: [PATCH 18/19] Merge pull request #1529 from aanand/update-dockerpty Update dockerpty to 0.3.4 (cherry picked from commit 95b2eaac042bb761b4f94c35a1af539467714098) Signed-off-by: Aanand Prasad --- requirements.txt | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index b9398848..d3909b76 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,6 @@ PyYAML==3.10 docker-py==1.2.2 -dockerpty==0.3.3 +dockerpty==0.3.4 docopt==0.6.1 requests==2.6.1 six==1.7.3 diff --git a/setup.py b/setup.py index 153275f6..9364f57f 100644 --- a/setup.py +++ b/setup.py @@ -31,7 +31,7 @@ install_requires = [ 'texttable >= 0.8.1, < 0.9', 'websocket-client >= 0.11.0, < 1.0', 'docker-py >= 1.2.2, < 1.3', - 'dockerpty >= 0.3.3, < 0.4', + 'dockerpty >= 0.3.4, < 0.4', 'six >= 1.3.0, < 2', ] From 1e963618b838f383fce2f117be920fb82d14a89e Mon Sep 17 00:00:00 2001 From: Aanand Prasad Date: Tue, 26 May 2015 12:43:23 +0100 Subject: [PATCH 19/19] Bump 1.3.0rc2 Signed-off-by: Aanand Prasad --- CHANGES.md | 43 +++++++++++++++++++++++++++++++++++++++++++ compose/__init__.py | 2 +- docs/install.md | 14 +++++++++++++- 3 files changed, 57 insertions(+), 2 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 277a188a..44d00b7f 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,49 @@ Change log ========== +1.3.0 RC2 (2015-06-09) +---------------------- + +Firstly, two important notes: + +- **This release contains breaking changes, and you will need to either remove or migrate your existing containers before running your app** - see the [upgrading section of the install docs](https://github.com/docker/compose/blob/1.3.0rc1/docs/install.md#upgrading) for details. + +- Compose now requires Docker 1.6.0 or later. + +We've done a lot of work in this release to remove hacks and make Compose more stable: + +- Compose now uses Docker labels, rather than container names, to keep track of containers. This is both cleaner and more performant. + +- Compose no longer uses "intermediate containers" when recreating containers for a service. This makes `docker-compose up` less complex and more resilient to failure. + +There are some new features: + +- `docker-compose up` has an **experimental** new behaviour: it will only recreate containers for services whose configuration has changed in `docker-compose.yml`. This will eventually become the default, but for now you can take it for a spin: + + $ docker-compose up --x-smart-recreate + +- When invoked in a subdirectory of a project, `docker-compose` will now climb up through parent directories until it finds a `docker-compose.yml`. + +Several new configuration keys have been added to `docker-compose.yml`: + +- `dockerfile`, like `docker build --file`, lets you specify an alternate Dockerfile to use with `build`. +- `labels`, like `docker run --labels`, lets you add custom metadata to containers. +- `extra_hosts`, like `docker run --add-host`, lets you add entries to a container's `/etc/hosts` file. +- `pid: host`, like `docker run --pid=host`, lets you reuse the same PID namespace as the host machine. +- `cpuset`, like `docker run --cpuset-cpus`, lets you specify which CPUs to allow execution in. +- `read_only`, like `docker run --read-only`, lets you mount a container's filesystem as read-only. +- `security_opt`, like `docker run --security-opt`, lets you specify [security options](https://docs.docker.com/reference/run/#security-configuration). +- `log_driver`, like `docker run --log-driver`, lets you specify a [log driver](https://docs.docker.com/reference/run/#logging-drivers-log-driver). + +Many bugs have been fixed, including the following: + +- The output of `docker-compose run` was sometimes truncated, especially when running under Jenkins. +- A service's volumes would sometimes not update after volume configuration was changed in `docker-compose.yml`. +- Authenticating against third-party registries would sometimes fail. +- `docker-compose run --rm` would fail to remove the container if the service had a `restart` policy in place. + +Thanks @ahromis, @albers, @aleksandr-vin, @antoineco, @ccverak, @chernjie, @dnephin, @josephpage, @KyleJamesWalker, @lsowen, @mchasal, @sdake, @sherter, @stephenlawrence, @turtlemonvh, @vdemeester, @xuxinkun and @zwily! + 1.2.0 (2015-04-16) ------------------ diff --git a/compose/__init__.py b/compose/__init__.py index 045e7914..08fed55a 100644 --- a/compose/__init__.py +++ b/compose/__init__.py @@ -1,3 +1,3 @@ from __future__ import unicode_literals -__version__ = '1.3.0dev' +__version__ = '1.3.0rc2' diff --git a/docs/install.md b/docs/install.md index a521ec06..869b7f7c 100644 --- a/docs/install.md +++ b/docs/install.md @@ -20,7 +20,7 @@ First, install Docker version 1.6 or greater: To install Compose, run the following commands: - curl -L https://github.com/docker/compose/releases/download/1.2.0/docker-compose-`uname -s`-`uname -m` > /usr/local/bin/docker-compose + curl -L https://github.com/docker/compose/releases/download/1.3.0rc2/docker-compose-`uname -s`-`uname -m` > /usr/local/bin/docker-compose chmod +x /usr/local/bin/docker-compose > Note: If you get a "Permission denied" error, your `/usr/local/bin` directory probably isn't writable and you'll need to install Compose as the superuser. Run `sudo -i`, then the two commands above, then `exit`. @@ -36,6 +36,18 @@ Compose can also be installed as a Python package: No further steps are required; Compose should now be successfully installed. You can test the installation by running `docker-compose --version`. +### Upgrading + +If you're coming from Compose 1.2 or earlier, you'll need to remove or migrate your existing containers after upgrading Compose. This is because, as of version 1.3, Compose uses Docker labels to keep track of containers, and so they need to be recreated with labels added. + +If Compose detects containers that were created without labels, it will refuse to run so that you don't end up with two sets of them. If you want to keep using your existing containers (for example, because they have data volumes you want to preserve) you can migrate them with the following command: + + docker-compose migrate-to-labels + +Alternatively, if you're not worried about keeping them, you can remove them - Compose will just create new ones. + + docker rm -f myapp_web_1 myapp_db_1 ... + ## Compose documentation - [User guide](index.md)