Compare commits

..

1 Commits

Author SHA1 Message Date
Aanand Prasad
ff3a5451fe Bump 1.2.0
Signed-off-by: Aanand Prasad <aanand.prasad@gmail.com>
2015-03-25 14:04:55 -07:00
21 changed files with 65 additions and 272 deletions

View File

@@ -1,8 +1,17 @@
Change log
==========
1.2.0 (2015-04-16)
------------------
1.2.0rc2 (2015-03-24)
---------------------
This is a release candidate for Compose 1.2.0.
On top of the changes listed below for RC1, a bug has been fixed where containers were being created with blank entries for "Dns" and "DnsSearch", causing DNS lookups from within a container to fail.
1.2.0rc1 (2015-03-23)
---------------------
This is a release candidate for Compose 1.2.0.
- `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).
@@ -10,12 +19,14 @@ Change log
- 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**.
- Filenames in `env_file` and volume host paths in `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:<service>`.
- `volumes_from` and `net: container:<service>` entries are taken into account when resolving dependencies, so `docker-compose up <service>` will correctly start all dependencies of `<service>`.
- Problems with authentication when using images from third-party registries have been fixed.
- `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`.

View File

@@ -23,9 +23,6 @@ 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/

View File

@@ -1,4 +1,4 @@
from __future__ import unicode_literals
from .service import Service # noqa:flake8
__version__ = '1.2.0'
__version__ = '1.2.0rc2'

View File

@@ -171,9 +171,6 @@ 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
@@ -192,29 +189,10 @@ 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']
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]
for k in ALLOWED_KEYS:
if k not in ['environment', 'volumes']:
if k in override:
d[k] = override[k]
return d
@@ -328,24 +306,11 @@ 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
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))
@@ -383,15 +348,6 @@ 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

View File

@@ -3,6 +3,7 @@ from __future__ import absolute_import
from collections import namedtuple
import logging
import re
import os
from operator import attrgetter
import sys
import six
@@ -585,7 +586,8 @@ def parse_repository_tag(s):
def build_volume_binding(volume_spec):
internal = {'bind': volume_spec.internal, 'ro': volume_spec.mode == 'ro'}
return volume_spec.external, internal
external = os.path.expanduser(volume_spec.external)
return os.path.abspath(os.path.expandvars(external)), internal
def build_port_bindings(ports):

View File

@@ -232,14 +232,14 @@ _docker-compose_run() {
compopt -o nospace
return
;;
--entrypoint|--user|-u)
--entrypoint)
return
;;
esac
case "$cur" in
-*)
COMPREPLY=( $( compgen -W "--allow-insecure-ssl -d --entrypoint -e --no-deps --rm --service-ports -T --user -u" -- "$cur" ) )
COMPREPLY=( $( compgen -W "--allow-insecure-ssl -d --entrypoint -e --no-deps --rm --service-ports -T" -- "$cur" ) )
;;
*)
__docker-compose_services_all

View File

@@ -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.2.0/contrib/completion/bash/docker-compose > /etc/bash_completion.d/docker-compose
curl -L https://raw.githubusercontent.com/docker/compose/1.2.0rc2/contrib/completion/bash/docker-compose > /etc/bash_completion.d/docker-compose
Completion will be available upon next login.

View File

@@ -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.2.0/docker-compose-`uname -s`-`uname -m` > /usr/local/bin/docker-compose
curl -L https://github.com/docker/compose/releases/download/1.2.0rc2/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

View File

@@ -29,9 +29,8 @@ image: a4bc65fd
### build
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.
Path to a directory containing a Dockerfile. 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.

View File

@@ -1,12 +1,8 @@
#!/bin/bash
#!/bin/sh
set -ex
TAG="docker-compose"
docker build -t "$TAG" .
docker run \
--rm \
--user=user \
--volume="$(pwd):/code" \
--entrypoint="script/build-linux-inner" \
"$TAG"
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

View File

@@ -1,10 +0,0 @@
#!/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

View File

@@ -1,18 +0,0 @@
#!/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

View File

@@ -4,6 +4,9 @@
set -e
>&2 echo "Validating DCO"
script/validate-dco
>&2 echo "Running lint checks"
flake8 compose
@@ -15,7 +18,7 @@ fi
for version in $DOCKER_VERSIONS; do
>&2 echo "Running tests against Docker $version"
docker run \
docker-1.5.0 run \
--rm \
--privileged \
--volume="/var/lib/docker" \

View File

@@ -4,7 +4,7 @@ if [ "$DOCKER_VERSION" == "" ]; then
DOCKER_VERSION="1.5.0"
fi
ln -fs "/usr/local/bin/docker-$DOCKER_VERSION" "/usr/local/bin/docker"
ln -s "/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.

View File

@@ -1,2 +0,0 @@
FROM busybox:latest
CMD echo "success"

View File

@@ -1,2 +0,0 @@
foo:
build: ../build-ctx/

View File

@@ -1,2 +1,2 @@
service:
build: .
build: tests/fixtures/dockerfile_with_entrypoint

View File

@@ -1,2 +1,2 @@
simple:
build: .
build: tests/fixtures/simple-dockerfile

View File

@@ -123,24 +123,6 @@ 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()

View File

@@ -39,150 +39,46 @@ 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):
class MergeTest(unittest.TestCase):
def test_merge_volumes_empty(self):
service_dict = config.merge_service_dicts({}, {})
self.assertNotIn('volumes', service_dict)
def test_no_override(self):
def test_merge_volumes_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_no_base(self):
def test_merge_volumes_no_base(self):
service_dict = config.merge_service_dicts(
{},
{'volumes': ['/bar:/code']},
)
self.assertEqual(set(service_dict['volumes']), set(['/bar:/code']))
def test_override_explicit_path(self):
def test_merge_volumes_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_add_explicit_path(self):
def test_merge_volumes_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_remove_explicit_path(self):
def test_merge_volumes_remove_explicit_path(self):
service_dict = config.merge_service_dicts(
{'volumes': ['/foo:/code', '/quux:/data']},
{'volumes': ['/bar:/code', '/data']},
)
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 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):
@@ -395,36 +291,3 @@ 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}])

View File

@@ -1,5 +1,6 @@
from __future__ import unicode_literals
from __future__ import absolute_import
import os
from .. import unittest
import mock
@@ -300,3 +301,18 @@ 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)))