summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorUlysses Souza <ulysses.souza@docker.com>2019-08-07 15:03:32 +0200
committerGitHub <noreply@github.com>2019-08-07 15:03:32 +0200
commit2c668e237d07b518630139b185f3337dbcbd5250 (patch)
treed40a414ae0d354703f06fe976b2e7519a937a1a1
parent85d940909e941915ea5a5e1e5c2f8bd0a086948e (diff)
parent661ac20e5d0a926fbca015133eda0574a9e4a1bd (diff)
Merge pull request #6835 from docker/bump-1.25.0-rc2
Bump 1.25.0-rc2
-rw-r--r--.circleci/config.yml2
-rw-r--r--CHANGELOG.md70
-rw-r--r--Dockerfile87
-rw-r--r--Dockerfile.armhf39
-rw-r--r--Dockerfile.run19
-rw-r--r--Jenkinsfile55
-rw-r--r--MAINTAINERS23
-rw-r--r--appveyor.yml6
-rw-r--r--compose/__init__.py2
-rw-r--r--compose/bundle.py49
-rw-r--r--compose/cli/command.py36
-rw-r--r--compose/cli/docker_client.py2
-rw-r--r--compose/cli/main.py78
-rw-r--r--compose/cli/utils.py2
-rw-r--r--compose/config/config.py63
-rw-r--r--compose/config/environment.py14
-rw-r--r--compose/config/interpolation.py12
-rw-r--r--compose/config/serialize.py20
-rw-r--r--compose/network.py6
-rw-r--r--compose/project.py42
-rw-r--r--compose/service.py55
-rw-r--r--compose/volume.py4
-rw-r--r--contrib/completion/bash/docker-compose12
-rw-r--r--contrib/completion/fish/docker-compose.fish1
-rwxr-xr-xcontrib/completion/zsh/_docker-compose3
-rwxr-xr-xcontrib/migration/migrate-compose-file-v1-to-v2.py6
-rwxr-xr-xdocker-compose-entrypoint.sh20
-rw-r--r--docs/README.md6
-rwxr-xr-xpyinstaller/ldd13
-rw-r--r--requirements-build.txt2
-rw-r--r--requirements-dev.txt3
-rw-r--r--requirements.txt10
-rwxr-xr-xscript/build/image11
-rwxr-xr-xscript/build/linux18
-rwxr-xr-xscript/build/linux-entrypoint42
-rwxr-xr-xscript/build/osx5
-rwxr-xr-xscript/build/test-image15
-rw-r--r--script/build/windows.ps16
-rwxr-xr-xscript/build/write-git-sha2
-rw-r--r--script/release/README.md2
-rwxr-xr-xscript/release/release.py8
-rw-r--r--script/release/release/const.py1
-rw-r--r--script/release/release/images.py127
-rw-r--r--script/release/release/repository.py3
-rwxr-xr-xscript/run/run.sh4
-rwxr-xr-xscript/setup/osx24
-rwxr-xr-xscript/test/all5
-rwxr-xr-xscript/test/ci3
-rwxr-xr-xscript/test/default7
-rw-r--r--setup.py24
-rw-r--r--tests/acceptance/cli_test.py119
-rw-r--r--tests/fixtures/UpperCaseDir/docker-compose.yml4
-rw-r--r--tests/fixtures/abort-on-container-exit-0/docker-compose.yml4
-rw-r--r--tests/fixtures/abort-on-container-exit-1/docker-compose.yml4
-rw-r--r--tests/fixtures/build-args/Dockerfile2
-rw-r--r--tests/fixtures/build-ctx/Dockerfile2
-rw-r--r--tests/fixtures/build-memory/Dockerfile2
-rw-r--r--tests/fixtures/build-multiple-composefile/a/Dockerfile2
-rw-r--r--tests/fixtures/build-multiple-composefile/b/Dockerfile2
-rw-r--r--tests/fixtures/default-env-file/.env24
-rw-r--r--tests/fixtures/dockerfile-with-volume/Dockerfile2
-rw-r--r--tests/fixtures/duplicate-override-yaml-files/docker-compose.yml4
-rw-r--r--tests/fixtures/echo-services/docker-compose.yml4
-rw-r--r--tests/fixtures/entrypoint-dockerfile/Dockerfile2
-rw-r--r--tests/fixtures/env-file-override/.env.conf2
-rw-r--r--tests/fixtures/env-file-override/.env.override1
-rw-r--r--tests/fixtures/env-file-override/docker-compose.yml6
-rw-r--r--tests/fixtures/environment-composefile/docker-compose.yml2
-rw-r--r--tests/fixtures/exit-code-from/docker-compose.yml4
-rw-r--r--tests/fixtures/expose-composefile/docker-compose.yml2
-rw-r--r--tests/fixtures/images-service-tag/Dockerfile2
-rw-r--r--tests/fixtures/logging-composefile-legacy/docker-compose.yml4
-rw-r--r--tests/fixtures/logging-composefile/docker-compose.yml4
-rw-r--r--tests/fixtures/logs-composefile/docker-compose.yml8
-rw-r--r--tests/fixtures/logs-restart-composefile/docker-compose.yml6
-rw-r--r--tests/fixtures/logs-tail-composefile/docker-compose.yml2
-rw-r--r--tests/fixtures/longer-filename-composefile/docker-compose.yaml2
-rw-r--r--tests/fixtures/multiple-composefiles/compose2.yml2
-rw-r--r--tests/fixtures/multiple-composefiles/docker-compose.yml4
-rw-r--r--tests/fixtures/networks/default-network-config.yml4
-rw-r--r--tests/fixtures/networks/external-default.yml4
-rw-r--r--tests/fixtures/no-links-composefile/docker-compose.yml6
-rw-r--r--tests/fixtures/override-files/docker-compose.yml4
-rw-r--r--tests/fixtures/override-files/extra.yml2
-rw-r--r--tests/fixtures/override-yaml-files/docker-compose.yml4
-rw-r--r--tests/fixtures/ports-composefile-scale/docker-compose.yml2
-rw-r--r--tests/fixtures/ports-composefile/docker-compose.yml2
-rw-r--r--tests/fixtures/ports-composefile/expanded-notation.yml2
-rw-r--r--tests/fixtures/ps-services-filter/docker-compose.yml2
-rw-r--r--tests/fixtures/run-labels/docker-compose.yml2
-rw-r--r--tests/fixtures/run-workdir/docker-compose.yml2
-rw-r--r--tests/fixtures/scale/docker-compose.yml8
-rw-r--r--tests/fixtures/simple-composefile-volume-ready/docker-compose.merge.yml2
-rw-r--r--tests/fixtures/simple-composefile-volume-ready/docker-compose.yml2
-rw-r--r--tests/fixtures/simple-composefile/digest.yml2
-rw-r--r--tests/fixtures/simple-composefile/docker-compose.yml2
-rw-r--r--tests/fixtures/simple-composefile/ignore-pull-failures.yml2
-rw-r--r--tests/fixtures/simple-composefile/pull-with-build.yml11
-rw-r--r--tests/fixtures/simple-failing-dockerfile/Dockerfile2
-rw-r--r--tests/fixtures/sleeps-composefile/docker-compose.yml4
-rw-r--r--tests/fixtures/stop-signal-composefile/docker-compose.yml2
-rw-r--r--tests/fixtures/tagless-image/Dockerfile2
-rw-r--r--tests/fixtures/top/docker-compose.yml4
-rw-r--r--tests/fixtures/unicode-environment/docker-compose.yml2
-rw-r--r--tests/fixtures/user-composefile/docker-compose.yml2
-rw-r--r--tests/fixtures/v2-dependencies/docker-compose.yml6
-rw-r--r--tests/fixtures/v2-full/Dockerfile2
-rw-r--r--tests/fixtures/v2-full/docker-compose.yml2
-rw-r--r--tests/fixtures/v2-simple/links-invalid.yml4
-rw-r--r--tests/fixtures/v2-simple/one-container.yml5
-rw-r--r--tests/helpers.py6
-rw-r--r--tests/integration/environment_test.py70
-rw-r--r--tests/integration/project_test.py206
-rw-r--r--tests/integration/service_test.py16
-rw-r--r--tests/integration/state_test.py154
-rw-r--r--tests/integration/testcases.py5
-rw-r--r--tests/unit/bundle_test.py19
-rw-r--r--tests/unit/cli/docker_client_test.py2
-rw-r--r--tests/unit/cli/main_test.py83
-rw-r--r--tests/unit/cli/utils_test.py21
-rw-r--r--tests/unit/config/config_test.py125
-rw-r--r--tests/unit/container_test.py7
-rw-r--r--tests/unit/network_test.py4
-rw-r--r--tests/unit/project_test.py87
-rw-r--r--tests/unit/service_test.py32
-rw-r--r--tox.ini2
126 files changed, 1559 insertions, 659 deletions
diff --git a/.circleci/config.yml b/.circleci/config.yml
index 08f8c42c..906b1c0d 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -13,7 +13,7 @@ jobs:
command: sudo pip install --upgrade tox==2.1.1 virtualenv==16.2.0
- run:
name: unit tests
- command: tox -e py27,py36,py37 -- tests/unit
+ command: tox -e py27,py37 -- tests/unit
build-osx-binary:
macos:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8f777c6c..89f02881 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,14 +1,78 @@
Change log
==========
-1.24.1 (2019-06-24)
+1.25.0-rc2 (2019-08-06)
-------------------
+### Features
+
+- Add tag `docker-compose:latest`
+
+- Add `docker-compose:<version>-alpine` image/tag
+
+- Add `docker-compose:<version>-debian` image/tag
+
+- Bumped `docker-py` 4.0.1
+
+- Supports `requests` up to 2.22.0 version
+
+- Drops empty tag on `build:cache_from`
+
+- `Dockerfile` now generates `libmusl` binaries for alpine
+
+- Only pull images that can't be built
+
+- Attribute `scale` can now accept `0` as a value
+
+- Added `--quiet` build flag
+
+- Added `--no-interpolate` to `docker-compose config`
+
+- Bump OpenSSL for macOS build (`1.1.0j` to `1.1.1a`)
+
+- Added `--no-rm` to `build` command
+
+- Added support for `credential_spec`
+
+- Resolve digests without pulling image
+
+- Upgrade `pyyaml` to `4.2b1`
+
+- Lowered severity to `warning` if `down` tries to remove nonexisting image
+
+- Use improved API fields for project events when possible
+
+- Update `setup.py` for modern `pypi/setuptools` and remove `pandoc` dependencies
+
+- Removed `Dockerfile.armhf` which is no longer needed
+
### Bugfixes
-- Fixed acceptance tests
+- Fixed stdin_open
+
+- Fixed `--remove-orphans` when used with `up --no-start`
+
+- Fixed `docker-compose ps --all`
+
+- Fixed `depends_on` dependency recreation behavior
+
+- Fixed bash completion for `build --memory`
+
+- Fixed misleading warning concerning env vars when performing an `exec` command
+
+- Fixed failure check in parallel_execute_watch
+
+- Fixed race condition after pulling image
+
+- Fixed error on duplicate mount points.
+
+- Fixed merge on networks section
+
+- Always connect Compose container to `stdin`
+
+- Fixed the presentation of failed services on 'docker-compose start' when containers are not available
-1.24.0 (2019-03-22)
+1.24.0 (2019-03-28)
-------------------
### Features
diff --git a/Dockerfile b/Dockerfile
index c5e7c815..ed9d74e5 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,36 +1,71 @@
-FROM docker:18.06.1 as docker
-FROM python:3.6
+ARG DOCKER_VERSION=18.09.7
+ARG PYTHON_VERSION=3.7.4
+ARG BUILD_ALPINE_VERSION=3.10
+ARG BUILD_DEBIAN_VERSION=slim-stretch
+ARG RUNTIME_ALPINE_VERSION=3.10.0
+ARG RUNTIME_DEBIAN_VERSION=stretch-20190708-slim
-RUN set -ex; \
- apt-get update -qq; \
- apt-get install -y \
- locales \
- python-dev \
- git
+ARG BUILD_PLATFORM=alpine
-COPY --from=docker /usr/local/bin/docker /usr/local/bin/docker
+FROM docker:${DOCKER_VERSION} AS docker-cli
-# Python3 requires a valid locale
-RUN echo "en_US.UTF-8 UTF-8" > /etc/locale.gen && locale-gen
-ENV LANG en_US.UTF-8
+FROM python:${PYTHON_VERSION}-alpine${BUILD_ALPINE_VERSION} AS build-alpine
+RUN apk add --no-cache \
+ bash \
+ build-base \
+ ca-certificates \
+ curl \
+ gcc \
+ git \
+ libc-dev \
+ libffi-dev \
+ libgcc \
+ make \
+ musl-dev \
+ openssl \
+ openssl-dev \
+ python2 \
+ python2-dev \
+ zlib-dev
+ENV BUILD_BOOTLOADER=1
-RUN useradd -d /home/user -m -s /bin/bash user
-WORKDIR /code/
+FROM python:${PYTHON_VERSION}-${BUILD_DEBIAN_VERSION} AS build-debian
+RUN apt-get update && apt-get install -y \
+ curl \
+ gcc \
+ git \
+ libc-dev \
+ libgcc-6-dev \
+ make \
+ openssl \
+ python2.7-dev
+FROM build-${BUILD_PLATFORM} AS build
+COPY docker-compose-entrypoint.sh /usr/local/bin/
+ENTRYPOINT ["sh", "/usr/local/bin/docker-compose-entrypoint.sh"]
+COPY --from=docker-cli /usr/local/bin/docker /usr/local/bin/docker
+WORKDIR /code/
# FIXME(chris-crone): virtualenv 16.3.0 breaks build, force 16.2.0 until fixed
RUN pip install virtualenv==16.2.0
-RUN pip install tox==2.1.1
+RUN pip install tox==2.9.1
-ADD requirements.txt /code/
-ADD requirements-dev.txt /code/
-ADD .pre-commit-config.yaml /code/
-ADD setup.py /code/
-ADD tox.ini /code/
-ADD compose /code/compose/
-ADD README.md /code/
+COPY requirements.txt .
+COPY requirements-dev.txt .
+COPY .pre-commit-config.yaml .
+COPY tox.ini .
+COPY setup.py .
+COPY README.md .
+COPY compose compose/
RUN tox --notest
+COPY . .
+ARG GIT_COMMIT=unknown
+ENV DOCKER_COMPOSE_GITSHA=$GIT_COMMIT
+RUN script/build/linux-entrypoint
-ADD . /code/
-RUN chown -R user /code/
-
-ENTRYPOINT ["/code/.tox/py36/bin/docker-compose"]
+FROM alpine:${RUNTIME_ALPINE_VERSION} AS runtime-alpine
+FROM debian:${RUNTIME_DEBIAN_VERSION} AS runtime-debian
+FROM runtime-${BUILD_PLATFORM} AS runtime
+COPY docker-compose-entrypoint.sh /usr/local/bin/
+ENTRYPOINT ["sh", "/usr/local/bin/docker-compose-entrypoint.sh"]
+COPY --from=docker-cli /usr/local/bin/docker /usr/local/bin/docker
+COPY --from=build /usr/local/bin/docker-compose /usr/local/bin/docker-compose
diff --git a/Dockerfile.armhf b/Dockerfile.armhf
deleted file mode 100644
index ee2ce894..00000000
--- a/Dockerfile.armhf
+++ /dev/null
@@ -1,39 +0,0 @@
-FROM python:3.6
-
-RUN set -ex; \
- apt-get update -qq; \
- apt-get install -y \
- locales \
- curl \
- python-dev \
- git
-
-RUN curl -fsSL -o dockerbins.tgz "https://download.docker.com/linux/static/stable/armhf/docker-17.12.0-ce.tgz" && \
- SHA256=f8de6378dad825b9fd5c3c2f949e791d22f918623c27a72c84fd6975a0e5d0a2; \
- echo "${SHA256} dockerbins.tgz" | sha256sum -c - && \
- tar xvf dockerbins.tgz docker/docker --strip-components 1 && \
- mv docker /usr/local/bin/docker && \
- chmod +x /usr/local/bin/docker && \
- rm dockerbins.tgz
-
-# Python3 requires a valid locale
-RUN echo "en_US.UTF-8 UTF-8" > /etc/locale.gen && locale-gen
-ENV LANG en_US.UTF-8
-
-RUN useradd -d /home/user -m -s /bin/bash user
-WORKDIR /code/
-
-RUN pip install tox==2.1.1
-
-ADD requirements.txt /code/
-ADD requirements-dev.txt /code/
-ADD .pre-commit-config.yaml /code/
-ADD setup.py /code/
-ADD tox.ini /code/
-ADD compose /code/compose/
-RUN tox --notest
-
-ADD . /code/
-RUN chown -R user /code/
-
-ENTRYPOINT ["/code/.tox/py36/bin/docker-compose"]
diff --git a/Dockerfile.run b/Dockerfile.run
deleted file mode 100644
index ccc86ea9..00000000
--- a/Dockerfile.run
+++ /dev/null
@@ -1,19 +0,0 @@
-FROM docker:18.06.1 as docker
-FROM alpine:3.8
-
-ENV GLIBC 2.28-r0
-
-RUN apk update && apk add --no-cache openssl ca-certificates curl libgcc && \
- curl -fsSL -o /etc/apk/keys/sgerrand.rsa.pub https://alpine-pkgs.sgerrand.com/sgerrand.rsa.pub && \
- curl -fsSL -o glibc-$GLIBC.apk https://github.com/sgerrand/alpine-pkg-glibc/releases/download/$GLIBC/glibc-$GLIBC.apk && \
- apk add --no-cache glibc-$GLIBC.apk && \
- ln -s /lib/libz.so.1 /usr/glibc-compat/lib/ && \
- ln -s /lib/libc.musl-x86_64.so.1 /usr/glibc-compat/lib && \
- ln -s /usr/lib/libgcc_s.so.1 /usr/glibc-compat/lib && \
- rm /etc/apk/keys/sgerrand.rsa.pub glibc-$GLIBC.apk && \
- apk del curl
-
-COPY --from=docker /usr/local/bin/docker /usr/local/bin/docker
-COPY dist/docker-compose-Linux-x86_64 /usr/local/bin/docker-compose
-
-ENTRYPOINT ["docker-compose"]
diff --git a/Jenkinsfile b/Jenkinsfile
index 04f5cfbd..4de276ad 100644
--- a/Jenkinsfile
+++ b/Jenkinsfile
@@ -1,29 +1,38 @@
#!groovy
-def image
-
-def buildImage = { ->
+def buildImage = { String baseImage ->
+ def image
wrappedNode(label: "ubuntu && !zfs", cleanWorkspace: true) {
- stage("build image") {
+ stage("build image for \"${baseImage}\"") {
checkout(scm)
- def imageName = "dockerbuildbot/compose:${gitCommit()}"
+ def imageName = "dockerbuildbot/compose:${baseImage}-${gitCommit()}"
image = docker.image(imageName)
try {
image.pull()
} catch (Exception exc) {
- image = docker.build(imageName, ".")
- image.push()
+ sh """GIT_COMMIT=\$(script/build/write-git-sha) && \\
+ docker build -t ${imageName} \\
+ --target build \\
+ --build-arg BUILD_PLATFORM="${baseImage}" \\
+ --build-arg GIT_COMMIT="${GIT_COMMIT}" \\
+ .\\
+ """
+ sh "docker push ${imageName}"
+ echo "${imageName}"
+ return imageName
}
}
}
+ echo "image.id: ${image.id}"
+ return image.id
}
-def get_versions = { int number ->
+def get_versions = { String imageId, int number ->
def docker_versions
wrappedNode(label: "ubuntu && !zfs") {
def result = sh(script: """docker run --rm \\
--entrypoint=/code/.tox/py27/bin/python \\
- ${image.id} \\
+ ${imageId} \\
/code/script/test/versions.py -n ${number} docker/docker-ce recent
""", returnStdout: true
)
@@ -35,9 +44,11 @@ def get_versions = { int number ->
def runTests = { Map settings ->
def dockerVersions = settings.get("dockerVersions", null)
def pythonVersions = settings.get("pythonVersions", null)
+ def baseImage = settings.get("baseImage", null)
+ def imageName = settings.get("image", null)
if (!pythonVersions) {
- throw new Exception("Need Python versions to test. e.g.: `runTests(pythonVersions: 'py27,py36')`")
+ throw new Exception("Need Python versions to test. e.g.: `runTests(pythonVersions: 'py27,py37')`")
}
if (!dockerVersions) {
throw new Exception("Need Docker versions to test. e.g.: `runTests(dockerVersions: 'all')`")
@@ -45,7 +56,7 @@ def runTests = { Map settings ->
{ ->
wrappedNode(label: "ubuntu && !zfs", cleanWorkspace: true) {
- stage("test python=${pythonVersions} / docker=${dockerVersions}") {
+ stage("test python=${pythonVersions} / docker=${dockerVersions} / baseImage=${baseImage}") {
checkout(scm)
def storageDriver = sh(script: 'docker info | awk -F \': \' \'$1 == "Storage Driver" { print $2; exit }\'', returnStdout: true).trim()
echo "Using local system's storage driver: ${storageDriver}"
@@ -55,13 +66,13 @@ def runTests = { Map settings ->
--privileged \\
--volume="\$(pwd)/.git:/code/.git" \\
--volume="/var/run/docker.sock:/var/run/docker.sock" \\
- -e "TAG=${image.id}" \\
+ -e "TAG=${imageName}" \\
-e "STORAGE_DRIVER=${storageDriver}" \\
-e "DOCKER_VERSIONS=${dockerVersions}" \\
-e "BUILD_NUMBER=\$BUILD_TAG" \\
-e "PY_TEST_VERSIONS=${pythonVersions}" \\
--entrypoint="script/test/ci" \\
- ${image.id} \\
+ ${imageName} \\
--verbose
"""
}
@@ -69,16 +80,16 @@ def runTests = { Map settings ->
}
}
-buildImage()
-
def testMatrix = [failFast: true]
-def docker_versions = get_versions(2)
-
-for (int i = 0; i < docker_versions.length; i++) {
- def dockerVersion = docker_versions[i]
- testMatrix["${dockerVersion}_py27"] = runTests([dockerVersions: dockerVersion, pythonVersions: "py27"])
- testMatrix["${dockerVersion}_py36"] = runTests([dockerVersions: dockerVersion, pythonVersions: "py36"])
- testMatrix["${dockerVersion}_py37"] = runTests([dockerVersions: dockerVersion, pythonVersions: "py37"])
+def baseImages = ['alpine', 'debian']
+def pythonVersions = ['py27', 'py37']
+baseImages.each { baseImage ->
+ def imageName = buildImage(baseImage)
+ get_versions(imageName, 2).each { dockerVersion ->
+ pythonVersions.each { pyVersion ->
+ testMatrix["${baseImage}_${dockerVersion}_${pyVersion}"] = runTests([baseImage: baseImage, image: imageName, dockerVersions: dockerVersion, pythonVersions: pyVersion])
+ }
+ }
}
parallel(testMatrix)
diff --git a/MAINTAINERS b/MAINTAINERS
index 7aedd46e..5d4bd6a6 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -11,9 +11,8 @@
[Org]
[Org."Core maintainers"]
people = [
- "mefyl",
- "mnottale",
- "shin-",
+ "rumpl",
+ "ulyssessouza",
]
[Org.Alumni]
people = [
@@ -34,6 +33,10 @@
# including muti-file support, variable interpolation, secrets
# emulation and many more
"dnephin",
+
+ "shin-",
+ "mefyl",
+ "mnottale",
]
[people]
@@ -74,7 +77,17 @@
Email = "mazz@houseofmnowster.com"
GitHub = "mnowster"
- [People.shin-]
+ [people.rumpl]
+ Name = "Djordje Lukic"
+ Email = "djordje.lukic@docker.com"
+ GitHub = "rumpl"
+
+ [people.shin-]
Name = "Joffrey F"
- Email = "joffrey@docker.com"
+ Email = "f.joffrey@gmail.com"
GitHub = "shin-"
+
+ [people.ulyssessouza]
+ Name = "Ulysses Domiciano Souza"
+ Email = "ulysses.souza@docker.com"
+ GitHub = "ulyssessouza"
diff --git a/appveyor.yml b/appveyor.yml
index da80d01d..04a40e9c 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -2,15 +2,15 @@
version: '{branch}-{build}'
install:
- - "SET PATH=C:\\Python36-x64;C:\\Python36-x64\\Scripts;%PATH%"
+ - "SET PATH=C:\\Python37-x64;C:\\Python37-x64\\Scripts;%PATH%"
- "python --version"
- - "pip install tox==2.9.1 virtualenv==15.1.0"
+ - "pip install tox==2.9.1 virtualenv==16.2.0"
# Build the binary after tests
build: false
test_script:
- - "tox -e py27,py36,py37 -- tests/unit"
+ - "tox -e py27,py37 -- tests/unit"
- ps: ".\\script\\build\\windows.ps1"
artifacts:
diff --git a/compose/__init__.py b/compose/__init__.py
index 6a40e150..df0fd3fb 100644
--- a/compose/__init__.py
+++ b/compose/__init__.py
@@ -1,4 +1,4 @@
from __future__ import absolute_import
from __future__ import unicode_literals
-__version__ = '1.24.1'
+__version__ = '1.25.0-rc2'
diff --git a/compose/bundle.py b/compose/bundle.py
index 937a3708..77cb37aa 100644
--- a/compose/bundle.py
+++ b/compose/bundle.py
@@ -95,19 +95,10 @@ def get_image_digest(service, allow_push=False):
if separator == '@':
return service.options['image']
- try:
- image = service.image()
- except NoSuchImageError:
- action = 'build' if 'build' in service.options else 'pull'
- raise UserError(
- "Image not found for service '{service}'. "
- "You might need to run `docker-compose {action} {service}`."
- .format(service=service.name, action=action))
+ digest = get_digest(service)
- if image['RepoDigests']:
- # TODO: pick a digest based on the image tag if there are multiple
- # digests
- return image['RepoDigests'][0]
+ if digest:
+ return digest
if 'build' not in service.options:
raise NeedsPull(service.image_name, service.name)
@@ -118,6 +109,32 @@ def get_image_digest(service, allow_push=False):
return push_image(service)
+def get_digest(service):
+ digest = None
+ try:
+ image = service.image()
+ # TODO: pick a digest based on the image tag if there are multiple
+ # digests
+ if image['RepoDigests']:
+ digest = image['RepoDigests'][0]
+ except NoSuchImageError:
+ try:
+ # Fetch the image digest from the registry
+ distribution = service.get_image_registry_data()
+
+ if distribution['Descriptor']['digest']:
+ digest = '{image_name}@{digest}'.format(
+ image_name=service.image_name,
+ digest=distribution['Descriptor']['digest']
+ )
+ except NoSuchImageError:
+ raise UserError(
+ "Digest not found for service '{service}'. "
+ "Repository does not exist or may require 'docker login'"
+ .format(service=service.name))
+ return digest
+
+
def push_image(service):
try:
digest = service.push()
@@ -147,10 +164,10 @@ def push_image(service):
def to_bundle(config, image_digests):
if config.networks:
- log.warn("Unsupported top level key 'networks' - ignoring")
+ log.warning("Unsupported top level key 'networks' - ignoring")
if config.volumes:
- log.warn("Unsupported top level key 'volumes' - ignoring")
+ log.warning("Unsupported top level key 'volumes' - ignoring")
config = denormalize_config(config)
@@ -175,7 +192,7 @@ def convert_service_to_bundle(name, service_dict, image_digest):
continue
if key not in SUPPORTED_KEYS:
- log.warn("Unsupported key '{}' in services.{} - ignoring".format(key, name))
+ log.warning("Unsupported key '{}' in services.{} - ignoring".format(key, name))
continue
if key == 'environment':
@@ -222,7 +239,7 @@ def make_service_networks(name, service_dict):
for network_name, network_def in get_network_defs_for_service(service_dict).items():
for key in network_def.keys():
- log.warn(
+ log.warning(
"Unsupported key '{}' in services.{}.networks.{} - ignoring"
.format(key, name, network_name))
diff --git a/compose/cli/command.py b/compose/cli/command.py
index 339a65c5..2f38fe5a 100644
--- a/compose/cli/command.py
+++ b/compose/cli/command.py
@@ -21,10 +21,27 @@ from .utils import get_version_info
log = logging.getLogger(__name__)
-
-def project_from_options(project_dir, options):
+SILENT_COMMANDS = {
+ 'events',
+ 'exec',
+ 'kill',
+ 'logs',
+ 'pause',
+ 'ps',
+ 'restart',
+ 'rm',
+ 'start',
+ 'stop',
+ 'top',
+ 'unpause',
+}
+
+
+def project_from_options(project_dir, options, additional_options={}):
override_dir = options.get('--project-directory')
- environment = Environment.from_env_file(override_dir or project_dir)
+ environment_file = options.get('--env-file')
+ environment = Environment.from_env_file(override_dir or project_dir, environment_file)
+ environment.silent = options.get('COMMAND', None) in SILENT_COMMANDS
set_parallel_limit(environment)
host = options.get('--host')
@@ -40,6 +57,7 @@ def project_from_options(project_dir, options):
environment=environment,
override_dir=override_dir,
compatibility=options.get('--compatibility'),
+ interpolate=(not additional_options.get('--no-interpolate'))
)
@@ -59,15 +77,17 @@ def set_parallel_limit(environment):
parallel.GlobalLimit.set_global_limit(parallel_limit)
-def get_config_from_options(base_dir, options):
+def get_config_from_options(base_dir, options, additional_options={}):
override_dir = options.get('--project-directory')
- environment = Environment.from_env_file(override_dir or base_dir)
+ environment_file = options.get('--env-file')
+ environment = Environment.from_env_file(override_dir or base_dir, environment_file)
config_path = get_config_path_from_options(
base_dir, options, environment
)
return config.load(
config.find(base_dir, config_path, environment, override_dir),
- options.get('--compatibility')
+ options.get('--compatibility'),
+ not additional_options.get('--no-interpolate')
)
@@ -105,14 +125,14 @@ def get_client(environment, verbose=False, version=None, tls_config=None, host=N
def get_project(project_dir, config_path=None, project_name=None, verbose=False,
host=None, tls_config=None, environment=None, override_dir=None,
- compatibility=False):
+ compatibility=False, interpolate=True):
if not environment:
environment = Environment.from_env_file(project_dir)
config_details = config.find(project_dir, config_path, environment, override_dir)
project_name = get_project_name(
config_details.working_dir, project_name, environment
)
- config_data = config.load(config_details, compatibility)
+ config_data = config.load(config_details, compatibility, interpolate)
api_version = environment.get(
'COMPOSE_API_VERSION',
diff --git a/compose/cli/docker_client.py b/compose/cli/docker_client.py
index a01704fd..a57a69b5 100644
--- a/compose/cli/docker_client.py
+++ b/compose/cli/docker_client.py
@@ -31,7 +31,7 @@ def get_tls_version(environment):
tls_attr_name = "PROTOCOL_{}".format(compose_tls_version)
if not hasattr(ssl, tls_attr_name):
- log.warn(
+ log.warning(
'The "{}" protocol is unavailable. You may need to update your '
'version of Python or OpenSSL. Falling back to TLSv1 (default).'
.format(compose_tls_version)
diff --git a/compose/cli/main.py b/compose/cli/main.py
index 78960179..477b57b5 100644
--- a/compose/cli/main.py
+++ b/compose/cli/main.py
@@ -208,6 +208,7 @@ class TopLevelCommand(object):
(default: the path of the Compose file)
--compatibility If set, Compose will attempt to convert keys
in v3 files to their non-Swarm equivalent
+ --env-file PATH Specify an alternate environment file
Commands:
build Build or rebuild services
@@ -246,6 +247,11 @@ class TopLevelCommand(object):
def project_dir(self):
return self.toplevel_options.get('--project-directory') or '.'
+ @property
+ def toplevel_environment(self):
+ environment_file = self.toplevel_options.get('--env-file')
+ return Environment.from_env_file(self.project_dir, environment_file)
+
def build(self, options):
"""
Build or rebuild services.
@@ -260,10 +266,12 @@ class TopLevelCommand(object):
--compress Compress the build context using gzip.
--force-rm Always remove intermediate containers.
--no-cache Do not use cache when building the image.
+ --no-rm Do not remove intermediate containers after a successful build.
--pull Always attempt to pull a newer version of the image.
-m, --memory MEM Sets memory limit for the build container.
--build-arg key=val Set build-time variables for services.
--parallel Build images in parallel.
+ -q, --quiet Don't print anything to STDOUT
"""
service_names = options['SERVICE']
build_args = options.get('--build-arg', None)
@@ -273,8 +281,7 @@ class TopLevelCommand(object):
'--build-arg is only supported when services are specified for API version < 1.25.'
' Please use a Compose file version > 2.2 or specify which services to build.'
)
- environment = Environment.from_env_file(self.project_dir)
- build_args = resolve_build_args(build_args, environment)
+ build_args = resolve_build_args(build_args, self.toplevel_environment)
self.project.build(
service_names=options['SERVICE'],
@@ -282,9 +289,11 @@ class TopLevelCommand(object):
pull=bool(options.get('--pull', False)),
force_rm=bool(options.get('--force-rm', False)),
memory=options.get('--memory'),
+ rm=not bool(options.get('--no-rm', False)),
build_args=build_args,
gzip=options.get('--compress', False),
parallel_build=options.get('--parallel', False),
+ silent=options.get('--quiet', False)
)
def bundle(self, options):
@@ -327,6 +336,7 @@ class TopLevelCommand(object):
Options:
--resolve-image-digests Pin image tags to digests.
+ --no-interpolate Don't interpolate environment variables
-q, --quiet Only validate the configuration, don't print
anything.
--services Print the service names, one per line.
@@ -336,11 +346,12 @@ class TopLevelCommand(object):
or use the wildcard symbol to display all services
"""
- compose_config = get_config_from_options('.', self.toplevel_options)
+ additional_options = {'--no-interpolate': options.get('--no-interpolate')}
+ compose_config = get_config_from_options('.', self.toplevel_options, additional_options)
image_digests = None
if options['--resolve-image-digests']:
- self.project = project_from_options('.', self.toplevel_options)
+ self.project = project_from_options('.', self.toplevel_options, additional_options)
with errors.handle_connection_errors(self.project.client):
image_digests = image_digests_for_project(self.project)
@@ -357,14 +368,14 @@ class TopLevelCommand(object):
if options['--hash'] is not None:
h = options['--hash']
- self.project = project_from_options('.', self.toplevel_options)
+ self.project = project_from_options('.', self.toplevel_options, additional_options)
services = [svc for svc in options['--hash'].split(',')] if h != '*' else None
with errors.handle_connection_errors(self.project.client):
for service in self.project.get_services(services):
print('{} {}'.format(service.name, service.config_hash))
return
- print(serialize_config(compose_config, image_digests))
+ print(serialize_config(compose_config, image_digests, not options['--no-interpolate']))
def create(self, options):
"""
@@ -383,7 +394,7 @@ class TopLevelCommand(object):
"""
service_names = options['SERVICE']
- log.warn(
+ log.warning(
'The create command is deprecated. '
'Use the up command with the --no-start flag instead.'
)
@@ -422,8 +433,7 @@ class TopLevelCommand(object):
-t, --timeout TIMEOUT Specify a shutdown timeout in seconds.
(default: 10)
"""
- environment = Environment.from_env_file(self.project_dir)
- ignore_orphans = environment.get_boolean('COMPOSE_IGNORE_ORPHANS')
+ ignore_orphans = self.toplevel_environment.get_boolean('COMPOSE_IGNORE_ORPHANS')
if ignore_orphans and options['--remove-orphans']:
raise UserError("COMPOSE_IGNORE_ORPHANS and --remove-orphans cannot be combined.")
@@ -480,8 +490,7 @@ class TopLevelCommand(object):
not supported in API < 1.25)
-w, --workdir DIR Path to workdir directory for this command.
"""
- environment = Environment.from_env_file(self.project_dir)
- use_cli = not environment.get_boolean('COMPOSE_INTERACTIVE_NO_CLI')
+ use_cli = not self.toplevel_environment.get_boolean('COMPOSE_INTERACTIVE_NO_CLI')
index = int(options.get('--index'))
service = self.project.get_service(options['SERVICE'])
detach = options.get('--detach')
@@ -504,7 +513,7 @@ class TopLevelCommand(object):
if IS_WINDOWS_PLATFORM or use_cli and not detach:
sys.exit(call_docker(
build_exec_command(options, container.id, command),
- self.toplevel_options)
+ self.toplevel_options, self.toplevel_environment)
)
create_exec_options = {
@@ -709,7 +718,8 @@ class TopLevelCommand(object):
if options['--all']:
containers = sorted(self.project.containers(service_names=options['SERVICE'],
- one_off=OneOffFilter.include, stopped=True))
+ one_off=OneOffFilter.include, stopped=True),
+ key=attrgetter('name'))
else:
containers = sorted(
self.project.containers(service_names=options['SERVICE'], stopped=True) +
@@ -753,7 +763,7 @@ class TopLevelCommand(object):
--include-deps Also pull services declared as dependencies
"""
if options.get('--parallel'):
- log.warn('--parallel option is deprecated and will be removed in future versions.')
+ log.warning('--parallel option is deprecated and will be removed in future versions.')
self.project.pull(
service_names=options['SERVICE'],
ignore_pull_failures=options.get('--ignore-pull-failures'),
@@ -794,7 +804,7 @@ class TopLevelCommand(object):
-a, --all Deprecated - no effect.
"""
if options.get('--all'):
- log.warn(
+ log.warning(
'--all flag is obsolete. This is now the default behavior '
'of `docker-compose rm`'
)
@@ -872,10 +882,12 @@ class TopLevelCommand(object):
else:
command = service.options.get('command')
+ options['stdin_open'] = service.options.get('stdin_open', True)
+
container_options = build_one_off_container_options(options, detach, command)
run_one_off_container(
container_options, self.project, service, options,
- self.toplevel_options, self.project_dir
+ self.toplevel_options, self.toplevel_environment
)
def scale(self, options):
@@ -904,7 +916,7 @@ class TopLevelCommand(object):
'Use the up command with the --scale flag instead.'
)
else:
- log.warn(
+ log.warning(
'The scale command is deprecated. '
'Use the up command with the --scale flag instead.'
)
@@ -1050,8 +1062,7 @@ class TopLevelCommand(object):
if detached and (cascade_stop or exit_value_from):
raise UserError("--abort-on-container-exit and -d cannot be combined.")
- environment = Environment.from_env_file(self.project_dir)
- ignore_orphans = environment.get_boolean('COMPOSE_IGNORE_ORPHANS')
+ ignore_orphans = self.toplevel_environment.get_boolean('COMPOSE_IGNORE_ORPHANS')
if ignore_orphans and remove_orphans:
raise UserError("COMPOSE_IGNORE_ORPHANS and --remove-orphans cannot be combined.")
@@ -1236,7 +1247,7 @@ def exitval_from_opts(options, project):
exit_value_from = options.get('--exit-code-from')
if exit_value_from:
if not options.get('--abort-on-container-exit'):
- log.warn('using --exit-code-from implies --abort-on-container-exit')
+ log.warning('using --exit-code-from implies --abort-on-container-exit')
options['--abort-on-container-exit'] = True
if exit_value_from not in [s.name for s in project.get_services()]:
log.error('No service named "%s" was found in your compose file.',
@@ -1271,7 +1282,7 @@ def build_one_off_container_options(options, detach, command):
container_options = {
'command': command,
'tty': not (detach or options['-T'] or not sys.stdin.isatty()),
- 'stdin_open': not detach,
+ 'stdin_open': options.get('stdin_open'),
'detach': detach,
}
@@ -1314,7 +1325,7 @@ def build_one_off_container_options(options, detach, command):
def run_one_off_container(container_options, project, service, options, toplevel_options,
- project_dir='.'):
+ toplevel_environment):
if not options['--no-deps']:
deps = service.get_dependency_names()
if deps:
@@ -1343,8 +1354,7 @@ def run_one_off_container(container_options, project, service, options, toplevel
if options['--rm']:
project.client.remove_container(container.id, force=True, v=True)
- environment = Environment.from_env_file(project_dir)
- use_cli = not environment.get_boolean('COMPOSE_INTERACTIVE_NO_CLI')
+ use_cli = not toplevel_environment.get_boolean('COMPOSE_INTERACTIVE_NO_CLI')
signals.set_signal_handler_to_shutdown()
signals.set_signal_handler_to_hang_up()
@@ -1353,8 +1363,8 @@ def run_one_off_container(container_options, project, service, options, toplevel
if IS_WINDOWS_PLATFORM or use_cli:
service.connect_container_to_networks(container, use_network_aliases)
exit_code = call_docker(
- ["start", "--attach", "--interactive", container.id],
- toplevel_options
+ get_docker_start_call(container_options, container.id),
+ toplevel_options, toplevel_environment
)
else:
operation = RunOperation(
@@ -1380,6 +1390,16 @@ def run_one_off_container(container_options, project, service, options, toplevel
sys.exit(exit_code)
+def get_docker_start_call(container_options, container_id):
+ docker_call = ["start"]
+ if not container_options.get('detach'):
+ docker_call.append("--attach")
+ if container_options.get('stdin_open'):
+ docker_call.append("--interactive")
+ docker_call.append(container_id)
+ return docker_call
+
+
def log_printer_from_project(
project,
containers,
@@ -1434,7 +1454,7 @@ def exit_if(condition, message, exit_code):
raise SystemExit(exit_code)
-def call_docker(args, dockeropts):
+def call_docker(args, dockeropts, environment):
executable_path = find_executable('docker')
if not executable_path:
raise UserError(errors.docker_not_found_msg("Couldn't find `docker` binary."))
@@ -1464,7 +1484,7 @@ def call_docker(args, dockeropts):
args = [executable_path] + tls_options + args
log.debug(" ".join(map(pipes.quote, args)))
- return subprocess.call(args)
+ return subprocess.call(args, env=environment)
def parse_scale_args(options):
@@ -1565,7 +1585,7 @@ def warn_for_swarm_mode(client):
# UCP does multi-node scheduling with traditional Compose files.
return
- log.warn(
+ log.warning(
"The Docker Engine you're using is running in swarm mode.\n\n"
"Compose does not use swarm mode to deploy services to multiple nodes in a swarm. "
"All containers will be scheduled on the current node.\n\n"
diff --git a/compose/cli/utils.py b/compose/cli/utils.py
index 4cc055cc..bd06beef 100644
--- a/compose/cli/utils.py
+++ b/compose/cli/utils.py
@@ -137,7 +137,7 @@ def human_readable_file_size(size):
if order >= len(suffixes):
order = len(suffixes) - 1
- return '{0:.3g} {1}'.format(
+ return '{0:.4g} {1}'.format(
size / float(1 << (order * 10)),
suffixes[order]
)
diff --git a/compose/config/config.py b/compose/config/config.py
index f3142d80..5202d002 100644
--- a/compose/config/config.py
+++ b/compose/config/config.py
@@ -198,9 +198,9 @@ class ConfigFile(namedtuple('_ConfigFile', 'filename config')):
version = self.config['version']
if isinstance(version, dict):
- log.warn('Unexpected type for "version" key in "{}". Assuming '
- '"version" is the name of a service, and defaulting to '
- 'Compose file version 1.'.format(self.filename))
+ log.warning('Unexpected type for "version" key in "{}". Assuming '
+ '"version" is the name of a service, and defaulting to '
+ 'Compose file version 1.'.format(self.filename))
return V1
if not isinstance(version, six.string_types):
@@ -318,8 +318,8 @@ def get_default_config_files(base_dir):
winner = candidates[0]
if len(candidates) > 1:
- log.warn("Found multiple config files with supported names: %s", ", ".join(candidates))
- log.warn("Using %s\n", winner)
+ log.warning("Found multiple config files with supported names: %s", ", ".join(candidates))
+ log.warning("Using %s\n", winner)
return [os.path.join(path, winner)] + get_default_override_file(path)
@@ -362,7 +362,7 @@ def check_swarm_only_config(service_dicts, compatibility=False):
def check_swarm_only_key(service_dicts, key):
services = [s for s in service_dicts if s.get(key)]
if services:
- log.warn(
+ log.warning(
warning_template.format(
services=", ".join(sorted(s['name'] for s in services)),
key=key
@@ -373,7 +373,7 @@ def check_swarm_only_config(service_dicts, compatibility=False):
check_swarm_only_key(service_dicts, 'configs')
-def load(config_details, compatibility=False):
+def load(config_details, compatibility=False, interpolate=True):
"""Load the configuration from a working directory and a list of
configuration files. Files are loaded in order, and merged on top
of each other to create the final configuration.
@@ -383,7 +383,7 @@ def load(config_details, compatibility=False):
validate_config_version(config_details.config_files)
processed_files = [
- process_config_file(config_file, config_details.environment)
+ process_config_file(config_file, config_details.environment, interpolate=interpolate)
for config_file in config_details.config_files
]
config_details = config_details._replace(config_files=processed_files)
@@ -505,7 +505,6 @@ def load_services(config_details, config_file, compatibility=False):
def interpolate_config_section(config_file, config, section, environment):
- validate_config_section(config_file.filename, config, section)
return interpolate_environment_variables(
config_file.version,
config,
@@ -514,38 +513,60 @@ def interpolate_config_section(config_file, config, section, environment):
)
-def process_config_file(config_file, environment, service_name=None):
- services = interpolate_config_section(
+def process_config_section(config_file, config, section, environment, interpolate):
+ validate_config_section(config_file.filename, config, section)
+ if interpolate:
+ return interpolate_environment_variables(
+ config_file.version,
+ config,
+ section,
+ environment
+ )
+ else:
+ return config
+
+
+def process_config_file(config_file, environment, service_name=None, interpolate=True):
+ services = process_config_section(
config_file,
config_file.get_service_dicts(),
'service',
- environment)
+ environment,
+ interpolate,
+ )
if config_file.version > V1:
processed_config = dict(config_file.config)
processed_config['services'] = services
- processed_config['volumes'] = interpolate_config_section(
+ processed_config['volumes'] = process_config_section(
config_file,
config_file.get_volumes(),
'volume',
- environment)
- processed_config['networks'] = interpolate_config_section(
+ environment,
+ interpolate,
+ )
+ processed_config['networks'] = process_config_section(
config_file,
config_file.get_networks(),
'network',
- environment)
+ environment,
+ interpolate,
+ )
if config_file.version >= const.COMPOSEFILE_V3_1:
- processed_config['secrets'] = interpolate_config_section(
+ processed_config['secrets'] = process_config_section(
config_file,
config_file.get_secrets(),
'secret',
- environment)
+ environment,
+ interpolate,
+ )
if config_file.version >= const.COMPOSEFILE_V3_3:
- processed_config['configs'] = interpolate_config_section(
+ processed_config['configs'] = process_config_section(
config_file,
config_file.get_configs(),
'config',
- environment
+ environment,
+ interpolate,
)
else:
processed_config = services
@@ -900,7 +921,7 @@ def finalize_service(service_config, service_names, version, environment, compat
service_dict
)
if ignored_keys:
- log.warn(
+ log.warning(
'The following deploy sub-keys are not supported in compatibility mode and have'
' been ignored: {}'.format(', '.join(ignored_keys))
)
diff --git a/compose/config/environment.py b/compose/config/environment.py
index bd52758f..696356f3 100644
--- a/compose/config/environment.py
+++ b/compose/config/environment.py
@@ -26,7 +26,7 @@ def split_env(env):
key = env
if re.search(r'\s', key):
raise ConfigurationError(
- "environment variable name '{}' may not contains whitespace.".format(key)
+ "environment variable name '{}' may not contain whitespace.".format(key)
)
return key, value
@@ -56,14 +56,18 @@ class Environment(dict):
def __init__(self, *args, **kwargs):
super(Environment, self).__init__(*args, **kwargs)
self.missing_keys = []
+ self.silent = False
@classmethod
- def from_env_file(cls, base_dir):
+ def from_env_file(cls, base_dir, env_file=None):
def _initialize():
result = cls()
if base_dir is None:
return result
- env_file_path = os.path.join(base_dir, '.env')
+ if env_file:
+ env_file_path = os.path.join(base_dir, env_file)
+ else:
+ env_file_path = os.path.join(base_dir, '.env')
try:
return cls(env_vars_from_file(env_file_path))
except EnvFileNotFound:
@@ -95,8 +99,8 @@ class Environment(dict):
return super(Environment, self).__getitem__(key.upper())
except KeyError:
pass
- if key not in self.missing_keys:
- log.warn(
+ if not self.silent and key not in self.missing_keys:
+ log.warning(
"The {} variable is not set. Defaulting to a blank string."
.format(key)
)
diff --git a/compose/config/interpolation.py b/compose/config/interpolation.py
index 0f878be1..18be8562 100644
--- a/compose/config/interpolation.py
+++ b/compose/config/interpolation.py
@@ -64,12 +64,12 @@ def interpolate_value(name, config_key, value, section, interpolator):
string=e.string))
except UnsetRequiredSubstitution as e:
raise ConfigurationError(
- 'Missing mandatory value for "{config_key}" option in {section} "{name}": {err}'.format(
- config_key=config_key,
- name=name,
- section=section,
- err=e.err
- )
+ 'Missing mandatory value for "{config_key}" option interpolating {value} '
+ 'in {section} "{name}": {err}'.format(config_key=config_key,
+ value=value,
+ name=name,
+ section=section,
+ err=e.err)
)
diff --git a/compose/config/serialize.py b/compose/config/serialize.py
index 8cb8a280..5776ce95 100644
--- a/compose/config/serialize.py
+++ b/compose/config/serialize.py
@@ -24,14 +24,12 @@ def serialize_dict_type(dumper, data):
def serialize_string(dumper, data):
- """ Ensure boolean-like strings are quoted in the output and escape $ characters """
+ """ Ensure boolean-like strings are quoted in the output """
representer = dumper.represent_str if six.PY3 else dumper.represent_unicode
if isinstance(data, six.binary_type):
data = data.decode('utf-8')
- data = data.replace('$', '$$')
-
if data.lower() in ('y', 'n', 'yes', 'no', 'on', 'off', 'true', 'false'):
# Empirically only y/n appears to be an issue, but this might change
# depending on which PyYaml version is being used. Err on safe side.
@@ -39,6 +37,12 @@ def serialize_string(dumper, data):
return representer(data)
+def serialize_string_escape_dollar(dumper, data):
+ """ Ensure boolean-like strings are quoted in the output and escape $ characters """
+ data = data.replace('$', '$$')
+ return serialize_string(dumper, data)
+
+
yaml.SafeDumper.add_representer(types.MountSpec, serialize_dict_type)
yaml.SafeDumper.add_representer(types.VolumeFromSpec, serialize_config_type)
yaml.SafeDumper.add_representer(types.VolumeSpec, serialize_config_type)
@@ -46,8 +50,6 @@ yaml.SafeDumper.add_representer(types.SecurityOpt, serialize_config_type)
yaml.SafeDumper.add_representer(types.ServiceSecret, serialize_dict_type)
yaml.SafeDumper.add_representer(types.ServiceConfig, serialize_dict_type)
yaml.SafeDumper.add_representer(types.ServicePort, serialize_dict_type)
-yaml.SafeDumper.add_representer(str, serialize_string)
-yaml.SafeDumper.add_representer(six.text_type, serialize_string)
def denormalize_config(config, image_digests=None):
@@ -93,7 +95,13 @@ def v3_introduced_name_key(key):
return V3_5
-def serialize_config(config, image_digests=None):
+def serialize_config(config, image_digests=None, escape_dollar=True):
+ if escape_dollar:
+ yaml.SafeDumper.add_representer(str, serialize_string_escape_dollar)
+ yaml.SafeDumper.add_representer(six.text_type, serialize_string_escape_dollar)
+ else:
+ yaml.SafeDumper.add_representer(str, serialize_string)
+ yaml.SafeDumper.add_representer(six.text_type, serialize_string)
return yaml.safe_dump(
denormalize_config(config, image_digests),
default_flow_style=False,
diff --git a/compose/network.py b/compose/network.py
index 2491a598..e0d711ff 100644
--- a/compose/network.py
+++ b/compose/network.py
@@ -231,7 +231,7 @@ def check_remote_network_config(remote, local):
if k.startswith('com.docker.'): # We are only interested in user-specified labels
continue
if remote_labels.get(k) != local_labels.get(k):
- log.warn(
+ log.warning(
'Network {}: label "{}" has changed. It may need to be'
' recreated.'.format(local.true_name, k)
)
@@ -276,7 +276,7 @@ class ProjectNetworks(object):
}
unused = set(networks) - set(service_networks) - {'default'}
if unused:
- log.warn(
+ log.warning(
"Some networks were defined but are not used by any service: "
"{}".format(", ".join(unused)))
return cls(service_networks, use_networking)
@@ -288,7 +288,7 @@ class ProjectNetworks(object):
try:
network.remove()
except NotFound:
- log.warn("Network %s not found.", network.true_name)
+ log.warning("Network %s not found.", network.true_name)
def initialize(self):
if not self.use_networking:
diff --git a/compose/project.py b/compose/project.py
index a7f2aa05..a608ffd7 100644
--- a/compose/project.py
+++ b/compose/project.py
@@ -355,18 +355,17 @@ class Project(object):
return containers
def build(self, service_names=None, no_cache=False, pull=False, force_rm=False, memory=None,
- build_args=None, gzip=False, parallel_build=False):
+ build_args=None, gzip=False, parallel_build=False, rm=True, silent=False):
services = []
for service in self.get_services(service_names):
if service.can_be_built():
services.append(service)
- else:
+ elif not silent:
log.info('%s uses an image, skipping' % service.name)
def build_service(service):
- service.build(no_cache, pull, force_rm, memory, build_args, gzip)
-
+ service.build(no_cache, pull, force_rm, memory, build_args, gzip, rm, silent)
if parallel_build:
_, errors = parallel.parallel_execute(
services,
@@ -587,8 +586,10 @@ class Project(object):
", ".join(updated_dependencies))
containers_stopped = any(
service.containers(stopped=True, filters={'status': ['created', 'exited']}))
- has_links = any(c.get('HostConfig.Links') for c in service.containers())
- if always_recreate_deps or containers_stopped or not has_links:
+ service_has_links = any(service.get_link_names())
+ container_has_links = any(c.get('HostConfig.Links') for c in service.containers())
+ should_recreate_for_links = service_has_links ^ container_has_links
+ if always_recreate_deps or containers_stopped or should_recreate_for_links:
plan = service.convergence_plan(ConvergenceStrategy.always)
else:
plan = service.convergence_plan(strategy)
@@ -602,6 +603,9 @@ class Project(object):
def pull(self, service_names=None, ignore_pull_failures=False, parallel_pull=False, silent=False,
include_deps=False):
services = self.get_services(service_names, include_deps)
+ images_to_build = {service.image_name for service in services if service.can_be_built()}
+ services_to_pull = [service for service in services if service.image_name not in images_to_build]
+
msg = not silent and 'Pulling' or None
if parallel_pull:
@@ -627,7 +631,7 @@ class Project(object):
)
_, errors = parallel.parallel_execute(
- services,
+ services_to_pull,
pull_service,
operator.attrgetter('name'),
msg,
@@ -640,7 +644,7 @@ class Project(object):
raise ProjectError(combined_errors)
else:
- for service in services:
+ for service in services_to_pull:
service.pull(ignore_pull_failures, silent=silent)
def push(self, service_names=None, ignore_push_failures=False):
@@ -686,7 +690,7 @@ class Project(object):
def find_orphan_containers(self, remove_orphans):
def _find():
- containers = self._labeled_containers()
+ containers = set(self._labeled_containers() + self._labeled_containers(stopped=True))
for ctnr in containers:
service_name = ctnr.labels.get(LABEL_SERVICE)
if service_name not in self.service_names:
@@ -697,7 +701,10 @@ class Project(object):
if remove_orphans:
for ctnr in orphans:
log.info('Removing orphan container "{0}"'.format(ctnr.name))
- ctnr.kill()
+ try:
+ ctnr.kill()
+ except APIError:
+ pass
ctnr.remove(force=True)
else:
log.warning(
@@ -725,10 +732,11 @@ class Project(object):
def build_container_operation_with_timeout_func(self, operation, options):
def container_operation_with_timeout(container):
- if options.get('timeout') is None:
+ _options = options.copy()
+ if _options.get('timeout') is None:
service = self.get_service(container.service)
- options['timeout'] = service.stop_timeout(None)
- return getattr(container, operation)(**options)
+ _options['timeout'] = service.stop_timeout(None)
+ return getattr(container, operation)(**_options)
return container_operation_with_timeout
@@ -771,13 +779,13 @@ def get_secrets(service, service_secrets, secret_defs):
.format(service=service, secret=secret.source))
if secret_def.get('external'):
- log.warn("Service \"{service}\" uses secret \"{secret}\" which is external. "
- "External secrets are not available to containers created by "
- "docker-compose.".format(service=service, secret=secret.source))
+ log.warning("Service \"{service}\" uses secret \"{secret}\" which is external. "
+ "External secrets are not available to containers created by "
+ "docker-compose.".format(service=service, secret=secret.source))
continue
if secret.uid or secret.gid or secret.mode:
- log.warn(
+ log.warning(
"Service \"{service}\" uses secret \"{secret}\" with uid, "
"gid, or mode. These fields are not supported by this "
"implementation of the Compose file".format(
diff --git a/compose/service.py b/compose/service.py
index 8c6702f1..0db35438 100644
--- a/compose/service.py
+++ b/compose/service.py
@@ -59,7 +59,6 @@ from .utils import parse_seconds_float
from .utils import truncate_id
from .utils import unique_everseen
-
log = logging.getLogger(__name__)
@@ -177,7 +176,7 @@ class Service(object):
network_mode=None,
networks=None,
secrets=None,
- scale=None,
+ scale=1,
pid_mode=None,
default_platform=None,
**options
@@ -192,7 +191,7 @@ class Service(object):
self.pid_mode = pid_mode or PidMode(None)
self.networks = networks or {}
self.secrets = secrets or []
- self.scale_num = scale or 1
+ self.scale_num = scale
self.default_platform = default_platform
self.options = options
@@ -241,15 +240,15 @@ class Service(object):
def show_scale_warnings(self, desired_num):
if self.custom_container_name and desired_num > 1:
- log.warn('The "%s" service is using the custom container name "%s". '
- 'Docker requires each container to have a unique name. '
- 'Remove the custom name to scale the service.'
- % (self.name, self.custom_container_name))
+ log.warning('The "%s" service is using the custom container name "%s". '
+ 'Docker requires each container to have a unique name. '
+ 'Remove the custom name to scale the service.'
+ % (self.name, self.custom_container_name))
if self.specifies_host_port() and desired_num > 1:
- log.warn('The "%s" service specifies a port on the host. If multiple containers '
- 'for this service are created on a single host, the port will clash.'
- % self.name)
+ log.warning('The "%s" service specifies a port on the host. If multiple containers '
+ 'for this service are created on a single host, the port will clash.'
+ % self.name)
def scale(self, desired_num, timeout=None):
"""
@@ -358,11 +357,17 @@ class Service(object):
raise NeedsBuildError(self)
self.build()
- log.warn(
+ log.warning(
"Image for service {} was built because it did not already exist. To "
"rebuild this image you must use `docker-compose build` or "
"`docker-compose up --build`.".format(self.name))
+ def get_image_registry_data(self):
+ try:
+ return self.client.inspect_distribution(self.image_name)
+ except APIError:
+ raise NoSuchImageError("Image '{}' not found".format(self.image_name))
+
def image(self):
try:
return self.client.inspect_image(self.image_name)
@@ -680,6 +685,7 @@ class Service(object):
'links': self.get_link_names(),
'net': self.network_mode.id,
'networks': self.networks,
+ 'secrets': self.secrets,
'volumes_from': [
(v.source.name, v.mode)
for v in self.volumes_from if isinstance(v.source, Service)
@@ -1043,8 +1049,11 @@ class Service(object):
return [build_spec(secret) for secret in self.secrets]
def build(self, no_cache=False, pull=False, force_rm=False, memory=None, build_args_override=None,
- gzip=False):
- log.info('Building %s' % self.name)
+ gzip=False, rm=True, silent=False):
+ output_stream = open(os.devnull, 'w')
+ if not silent:
+ output_stream = sys.stdout
+ log.info('Building %s' % self.name)
build_opts = self.options.get('build', {})
@@ -1064,12 +1073,12 @@ class Service(object):
build_output = self.client.build(
path=path,
tag=self.image_name,
- rm=True,
+ rm=rm,
forcerm=force_rm,
pull=pull,
nocache=no_cache,
dockerfile=build_opts.get('dockerfile', None),
- cache_from=build_opts.get('cache_from', None),
+ cache_from=self.get_cache_from(build_opts),
labels=build_opts.get('labels', None),
buildargs=build_args,
network_mode=build_opts.get('network', None),
@@ -1085,7 +1094,7 @@ class Service(object):
)
try:
- all_events = list(stream_output(build_output, sys.stdout))
+ all_events = list(stream_output(build_output, output_stream))
except StreamOutputError as e:
raise BuildError(self, six.text_type(e))
@@ -1107,6 +1116,12 @@ class Service(object):
return image_id
+ def get_cache_from(self, build_opts):
+ cache_from = build_opts.get('cache_from', None)
+ if cache_from is not None:
+ cache_from = [tag for tag in cache_from if tag]
+ return cache_from
+
def can_be_built(self):
return 'build' in self.options
@@ -1316,7 +1331,7 @@ class ServicePidMode(PidMode):
if containers:
return 'container:' + containers[0].id
- log.warn(
+ log.warning(
"Service %s is trying to use reuse the PID namespace "
"of another service that is not running." % (self.service_name)
)
@@ -1379,8 +1394,8 @@ class ServiceNetworkMode(object):
if containers:
return 'container:' + containers[0].id
- log.warn("Service %s is trying to use reuse the network stack "
- "of another service that is not running." % (self.id))
+ log.warning("Service %s is trying to use reuse the network stack "
+ "of another service that is not running." % (self.id))
return None
@@ -1531,7 +1546,7 @@ def warn_on_masked_volume(volumes_option, container_volumes, service):
volume.internal in container_volumes and
container_volumes.get(volume.internal) != volume.external
):
- log.warn((
+ log.warning((
"Service \"{service}\" is using volume \"{volume}\" from the "
"previous container. Host mapping \"{host_path}\" has no effect. "
"Remove the existing containers (with `docker-compose rm {service}`) "
diff --git a/compose/volume.py b/compose/volume.py
index 60c1e0fe..b02fc5d8 100644
--- a/compose/volume.py
+++ b/compose/volume.py
@@ -127,7 +127,7 @@ class ProjectVolumes(object):
try:
volume.remove()
except NotFound:
- log.warn("Volume %s not found.", volume.true_name)
+ log.warning("Volume %s not found.", volume.true_name)
def initialize(self):
try:
@@ -209,7 +209,7 @@ def check_remote_volume_config(remote, local):
if k.startswith('com.docker.'): # We are only interested in user-specified labels
continue
if remote_labels.get(k) != local_labels.get(k):
- log.warn(
+ log.warning(
'Volume {}: label "{}" has changed. It may need to be'
' recreated.'.format(local.name, k)
)
diff --git a/contrib/completion/bash/docker-compose b/contrib/completion/bash/docker-compose
index 2add0c9c..6dc47799 100644
--- a/contrib/completion/bash/docker-compose
+++ b/contrib/completion/bash/docker-compose
@@ -110,11 +110,14 @@ _docker_compose_build() {
__docker_compose_nospace
return
;;
+ --memory|-m)
+ return
+ ;;
esac
case "$cur" in
-*)
- COMPREPLY=( $( compgen -W "--build-arg --compress --force-rm --help --memory --no-cache --pull --parallel" -- "$cur" ) )
+ COMPREPLY=( $( compgen -W "--build-arg --compress --force-rm --help --memory -m --no-cache --no-rm --pull --parallel -q --quiet" -- "$cur" ) )
;;
*)
__docker_compose_complete_services --filter source=build
@@ -147,7 +150,7 @@ _docker_compose_config() {
;;
esac
- COMPREPLY=( $( compgen -W "--hash --help --quiet -q --resolve-image-digests --services --volumes" -- "$cur" ) )
+ COMPREPLY=( $( compgen -W "--hash --help --no-interpolate --quiet -q --resolve-image-digests --services --volumes" -- "$cur" ) )
}
@@ -181,6 +184,10 @@ _docker_compose_docker_compose() {
_filedir -d
return
;;
+ --env-file)
+ _filedir
+ return
+ ;;
$(__docker_compose_to_extglob "$daemon_options_with_args") )
return
;;
@@ -609,6 +616,7 @@ _docker_compose() {
--tlsverify
"
local daemon_options_with_args="
+ --env-file
--file -f
--host -H
--project-directory
diff --git a/contrib/completion/fish/docker-compose.fish b/contrib/completion/fish/docker-compose.fish
index 69ecc505..0566e16a 100644
--- a/contrib/completion/fish/docker-compose.fish
+++ b/contrib/completion/fish/docker-compose.fish
@@ -12,6 +12,7 @@ end
complete -c docker-compose -s f -l file -r -d 'Specify an alternate compose file'
complete -c docker-compose -s p -l project-name -x -d 'Specify an alternate project name'
+complete -c docker-compose -l env-file -r -d 'Specify an alternate environment file (default: .env)'
complete -c docker-compose -l verbose -d 'Show more output'
complete -c docker-compose -s H -l host -x -d 'Daemon socket to connect to'
complete -c docker-compose -l tls -d 'Use TLS; implied by --tlsverify'
diff --git a/contrib/completion/zsh/_docker-compose b/contrib/completion/zsh/_docker-compose
index d25256c1..faf40598 100755
--- a/contrib/completion/zsh/_docker-compose
+++ b/contrib/completion/zsh/_docker-compose
@@ -113,6 +113,7 @@ __docker-compose_subcommand() {
$opts_help \
"*--build-arg=[Set build-time variables for one service.]:<varname>=<value>: " \
'--force-rm[Always remove intermediate containers.]' \
+ '(--quiet -q)'{--quiet,-q}'[Curb build output]' \
'(--memory -m)'{--memory,-m}'[Memory limit for the build container.]' \
'--no-cache[Do not use cache when building the image.]' \
'--pull[Always attempt to pull a newer version of the image.]' \
@@ -340,6 +341,7 @@ _docker-compose() {
'(- :)'{-h,--help}'[Get help]' \
'*'{-f,--file}"[${file_description}]:file:_files -g '*.yml'" \
'(-p --project-name)'{-p,--project-name}'[Specify an alternate project name (default: directory name)]:project name:' \
+ '--env-file[Specify an alternate environment file (default: .env)]:env-file:_files' \
"--compatibility[If set, Compose will attempt to convert keys in v3 files to their non-Swarm equivalent]" \
'(- :)'{-v,--version}'[Print version and exit]' \
'--verbose[Show more output]' \
@@ -358,6 +360,7 @@ _docker-compose() {
local -a relevant_compose_flags relevant_compose_repeatable_flags relevant_docker_flags compose_options docker_options
relevant_compose_flags=(
+ "--env-file"
"--file" "-f"
"--host" "-H"
"--project-name" "-p"
diff --git a/contrib/migration/migrate-compose-file-v1-to-v2.py b/contrib/migration/migrate-compose-file-v1-to-v2.py
index c1785b0d..274b499b 100755
--- a/contrib/migration/migrate-compose-file-v1-to-v2.py
+++ b/contrib/migration/migrate-compose-file-v1-to-v2.py
@@ -44,7 +44,7 @@ def warn_for_links(name, service):
links = service.get('links')
if links:
example_service = links[0].partition(':')[0]
- log.warn(
+ log.warning(
"Service {name} has links, which no longer create environment "
"variables such as {example_service_upper}_PORT. "
"If you are using those in your application code, you should "
@@ -57,7 +57,7 @@ def warn_for_links(name, service):
def warn_for_external_links(name, service):
external_links = service.get('external_links')
if external_links:
- log.warn(
+ log.warning(
"Service {name} has external_links: {ext}, which now work "
"slightly differently. In particular, two containers must be "
"connected to at least one network in common in order to "
@@ -107,7 +107,7 @@ def rewrite_volumes_from(service, service_names):
def create_volumes_section(data):
named_volumes = get_named_volumes(data['services'])
if named_volumes:
- log.warn(
+ log.warning(
"Named volumes ({names}) must be explicitly declared. Creating a "
"'volumes' section with declarations.\n\n"
"For backwards-compatibility, they've been declared as external. "
diff --git a/docker-compose-entrypoint.sh b/docker-compose-entrypoint.sh
new file mode 100755
index 00000000..84436fa0
--- /dev/null
+++ b/docker-compose-entrypoint.sh
@@ -0,0 +1,20 @@
+#!/bin/sh
+set -e
+
+# first arg is `-f` or `--some-option`
+if [ "${1#-}" != "$1" ]; then
+ set -- docker-compose "$@"
+fi
+
+# if our command is a valid Docker subcommand, let's invoke it through Docker instead
+# (this allows for "docker run docker ps", etc)
+if docker-compose help "$1" > /dev/null 2>&1; then
+ set -- docker-compose "$@"
+fi
+
+# if we have "--link some-docker:docker" and not DOCKER_HOST, let's set DOCKER_HOST automatically
+if [ -z "$DOCKER_HOST" -a "$DOCKER_PORT_2375_TCP" ]; then
+ export DOCKER_HOST='tcp://docker:2375'
+fi
+
+exec "$@"
diff --git a/docs/README.md b/docs/README.md
index 50c91d20..accc7c23 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -6,11 +6,9 @@ The documentation for Compose has been merged into
The docs for Compose are now here:
https://github.com/docker/docker.github.io/tree/master/compose
-Please submit pull requests for unpublished features on the `vnext-compose` branch (https://github.com/docker/docker.github.io/tree/vnext-compose).
+Please submit pull requests for unreleased features/changes on the `master` branch (https://github.com/docker/docker.github.io/tree/master), please prefix the PR title with `[WIP]` to indicate that it relates to an unreleased change.
-If you submit a PR to this codebase that has a docs impact, create a second docs PR on `docker.github.io`. Use the docs PR template provided (coming soon - watch this space).
-
-PRs for typos, additional information, etc. for already-published features should be labeled as `okay-to-publish` (we are still settling on a naming convention, will provide a label soon). You can submit these PRs either to `vnext-compose` or directly to `master` on `docker.github.io`
+If you submit a PR to this codebase that has a docs impact, create a second docs PR on `docker.github.io`. Use the docs PR template provided.
As always, the docs remain open-source and we appreciate your feedback and
pull requests!
diff --git a/pyinstaller/ldd b/pyinstaller/ldd
new file mode 100755
index 00000000..3f10ad27
--- /dev/null
+++ b/pyinstaller/ldd
@@ -0,0 +1,13 @@
+#!/bin/sh
+
+# From http://wiki.musl-libc.org/wiki/FAQ#Q:_where_is_ldd_.3F
+#
+# Musl's dynlinker comes with ldd functionality built in. just create a
+# symlink from ld-musl-$ARCH.so to /bin/ldd. If the dynlinker was started
+# as "ldd", it will detect that and print the appropriate DSO information.
+#
+# Instead, this string replaced "ldd" with the package so that pyinstaller
+# can find the actual lib.
+exec /usr/bin/ldd "$@" | \
+ sed -r 's/([^[:space:]]+) => ldd/\1 => \/lib\/\1/g' | \
+ sed -r 's/ldd \(.*\)//g'
diff --git a/requirements-build.txt b/requirements-build.txt
index e5a77e79..9161fadf 100644
--- a/requirements-build.txt
+++ b/requirements-build.txt
@@ -1 +1 @@
-pyinstaller==3.3.1
+pyinstaller==3.4
diff --git a/requirements-dev.txt b/requirements-dev.txt
index bfb94115..27b71a26 100644
--- a/requirements-dev.txt
+++ b/requirements-dev.txt
@@ -1,5 +1,6 @@
coverage==4.4.2
+ddt==1.2.0
flake8==3.5.0
-mock==2.0.0
+mock==3.0.5
pytest==3.6.3
pytest-cov==2.5.1
diff --git a/requirements.txt b/requirements.txt
index 6007ee3f..e5b6883e 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -3,7 +3,7 @@ cached-property==1.3.0
certifi==2017.4.17
chardet==3.0.4
colorama==0.4.0; sys_platform == 'win32'
-docker==3.7.3
+docker==4.0.1
docker-pycreds==0.4.0
dockerpty==0.4.1
docopt==0.6.2
@@ -17,8 +17,8 @@ pypiwin32==219; sys_platform == 'win32' and python_version < '3.6'
pypiwin32==223; sys_platform == 'win32' and python_version >= '3.6'
PySocks==1.6.7
PyYAML==4.2b1
-requests==2.20.0
+requests==2.22.0
six==1.10.0
-texttable==0.9.1
-urllib3==1.21.1; python_version == '3.3'
-websocket-client==0.56.0
+texttable==1.6.2
+urllib3==1.24.2; python_version == '3.3'
+websocket-client==0.32.0
diff --git a/script/build/image b/script/build/image
index a3198c99..fb3f856e 100755
--- a/script/build/image
+++ b/script/build/image
@@ -7,11 +7,14 @@ if [ -z "$1" ]; then
exit 1
fi
-TAG=$1
+TAG="$1"
VERSION="$(python setup.py --version)"
-./script/build/write-git-sha
+DOCKER_COMPOSE_GITSHA="$(script/build/write-git-sha)"
+echo "${DOCKER_COMPOSE_GITSHA}" > compose/GITSHA
python setup.py sdist bdist_wheel
-./script/build/linux
-docker build -t docker/compose:$TAG -f Dockerfile.run .
+
+docker build \
+ --build-arg GIT_COMMIT="${DOCKER_COMPOSE_GITSHA}" \
+ -t "${TAG}" .
diff --git a/script/build/linux b/script/build/linux
index 056940ad..28065da0 100755
--- a/script/build/linux
+++ b/script/build/linux
@@ -4,10 +4,14 @@ set -ex
./script/clean
-TAG="docker-compose"
-docker build -t "$TAG" .
-docker run \
- --rm --entrypoint="script/build/linux-entrypoint" \
- -v $(pwd)/dist:/code/dist \
- -v $(pwd)/.git:/code/.git \
- "$TAG"
+DOCKER_COMPOSE_GITSHA="$(script/build/write-git-sha)"
+TAG="docker/compose:tmp-glibc-linux-binary-${DOCKER_COMPOSE_GITSHA}"
+
+docker build -t "${TAG}" . \
+ --build-arg BUILD_PLATFORM=debian \
+ --build-arg GIT_COMMIT="${DOCKER_COMPOSE_GITSHA}"
+TMP_CONTAINER=$(docker create "${TAG}")
+mkdir -p dist
+docker cp "${TMP_CONTAINER}":/usr/local/bin/docker-compose dist/docker-compose-Linux-x86_64
+docker container rm -f "${TMP_CONTAINER}"
+docker image rm -f "${TAG}"
diff --git a/script/build/linux-entrypoint b/script/build/linux-entrypoint
index 0e3c7ec1..1c5438d8 100755
--- a/script/build/linux-entrypoint
+++ b/script/build/linux-entrypoint
@@ -2,14 +2,38 @@
set -ex
-TARGET=dist/docker-compose-$(uname -s)-$(uname -m)
-VENV=/code/.tox/py36
+CODE_PATH=/code
+VENV="${CODE_PATH}"/.tox/py37
-mkdir -p `pwd`/dist
-chmod 777 `pwd`/dist
+cd "${CODE_PATH}"
+mkdir -p dist
+chmod 777 dist
-$VENV/bin/pip install -q -r requirements-build.txt
-./script/build/write-git-sha
-su -c "$VENV/bin/pyinstaller docker-compose.spec" user
-mv dist/docker-compose $TARGET
-$TARGET version
+"${VENV}"/bin/pip3 install -q -r requirements-build.txt
+
+# TODO(ulyssessouza) To check if really needed
+if [ -z "${DOCKER_COMPOSE_GITSHA}" ]; then
+ DOCKER_COMPOSE_GITSHA="$(script/build/write-git-sha)"
+fi
+echo "${DOCKER_COMPOSE_GITSHA}" > compose/GITSHA
+
+export PATH="${CODE_PATH}/pyinstaller:${PATH}"
+
+if [ ! -z "${BUILD_BOOTLOADER}" ]; then
+ # Build bootloader for alpine
+ git clone --single-branch --branch master https://github.com/pyinstaller/pyinstaller.git /tmp/pyinstaller
+ cd /tmp/pyinstaller/bootloader
+ git checkout v3.4
+ "${VENV}"/bin/python3 ./waf configure --no-lsb all
+ "${VENV}"/bin/pip3 install ..
+ cd "${CODE_PATH}"
+ rm -Rf /tmp/pyinstaller
+else
+ echo "NOT compiling bootloader!!!"
+fi
+
+"${VENV}"/bin/pyinstaller --exclude-module pycrypto --exclude-module PyInstaller docker-compose.spec
+ls -la dist/
+ldd dist/docker-compose
+mv dist/docker-compose /usr/local/bin
+docker-compose version
diff --git a/script/build/osx b/script/build/osx
index c62b2702..52991458 100755
--- a/script/build/osx
+++ b/script/build/osx
@@ -5,11 +5,12 @@ TOOLCHAIN_PATH="$(realpath $(dirname $0)/../../build/toolchain)"
rm -rf venv
-virtualenv -p ${TOOLCHAIN_PATH}/bin/python3 venv
+virtualenv -p "${TOOLCHAIN_PATH}"/bin/python3 venv
venv/bin/pip install -r requirements.txt
venv/bin/pip install -r requirements-build.txt
venv/bin/pip install --no-deps .
-./script/build/write-git-sha
+DOCKER_COMPOSE_GITSHA="$(script/build/write-git-sha)"
+echo "${DOCKER_COMPOSE_GITSHA}" > compose/GITSHA
venv/bin/pyinstaller docker-compose.spec
mv dist/docker-compose dist/docker-compose-Darwin-x86_64
dist/docker-compose-Darwin-x86_64 version
diff --git a/script/build/test-image b/script/build/test-image
index a2eb62cd..4964a5f9 100755
--- a/script/build/test-image
+++ b/script/build/test-image
@@ -7,11 +7,12 @@ if [ -z "$1" ]; then
exit 1
fi
-TAG=$1
+TAG="$1"
+IMAGE="docker/compose-tests"
-docker build -t docker-compose-tests:tmp .
-ctnr_id=$(docker create --entrypoint=tox docker-compose-tests:tmp)
-docker commit $ctnr_id docker/compose-tests:latest
-docker tag docker/compose-tests:latest docker/compose-tests:$TAG
-docker rm -f $ctnr_id
-docker rmi -f docker-compose-tests:tmp
+DOCKER_COMPOSE_GITSHA="$(script/build/write-git-sha)"
+docker build -t "${IMAGE}:${TAG}" . \
+ --target build \
+ --build-arg BUILD_PLATFORM="debian" \
+ --build-arg GIT_COMMIT="${DOCKER_COMPOSE_GITSHA}"
+docker tag "${IMAGE}":"${TAG}" "${IMAGE}":latest
diff --git a/script/build/windows.ps1 b/script/build/windows.ps1
index 41dc51e3..4c7a8bed 100644
--- a/script/build/windows.ps1
+++ b/script/build/windows.ps1
@@ -6,17 +6,17 @@
#
# http://git-scm.com/download/win
#
-# 2. Install Python 3.6.4:
+# 2. Install Python 3.7.2:
#
# https://www.python.org/downloads/
#
-# 3. Append ";C:\Python36;C:\Python36\Scripts" to the "Path" environment variable:
+# 3. Append ";C:\Python37;C:\Python37\Scripts" to the "Path" environment variable:
#
# https://www.microsoft.com/resources/documentation/windows/xp/all/proddocs/en-us/sysdm_advancd_environmnt_addchange_variable.mspx?mfr=true
#
# 4. In Powershell, run the following commands:
#
-# $ pip install 'virtualenv>=15.1.0'
+# $ pip install 'virtualenv==16.2.0'
# $ Set-ExecutionPolicy -Scope CurrentUser RemoteSigned
#
# 5. Clone the repository:
diff --git a/script/build/write-git-sha b/script/build/write-git-sha
index be87f505..cac4b6fd 100755
--- a/script/build/write-git-sha
+++ b/script/build/write-git-sha
@@ -9,4 +9,4 @@ if [[ "${?}" != "0" ]]; then
echo "Couldn't get revision of the git repository. Setting to 'unknown' instead"
DOCKER_COMPOSE_GITSHA="unknown"
fi
-echo "${DOCKER_COMPOSE_GITSHA}" > compose/GITSHA
+echo "${DOCKER_COMPOSE_GITSHA}"
diff --git a/script/release/README.md b/script/release/README.md
index 0c6f12cb..97168d37 100644
--- a/script/release/README.md
+++ b/script/release/README.md
@@ -192,6 +192,8 @@ be handled manually by the operator:
- Bump the version in `compose/__init__.py` to the *next* minor version
number with `dev` appended. For example, if you just released `1.4.0`,
update it to `1.5.0dev`
+ - Update compose_version in [github.com/docker/docker.github.io/blob/master/_config.yml](https://github.com/docker/docker.github.io/blob/master/_config.yml) and [github.com/docker/docker.github.io/blob/master/_config_authoring.yml](https://github.com/docker/docker.github.io/blob/master/_config_authoring.yml)
+ - Update the release note in [github.com/docker/docker.github.io](https://github.com/docker/docker.github.io/blob/master/release-notes/docker-compose.md)
## Advanced options
diff --git a/script/release/release.py b/script/release/release.py
index 9db1a49d..a9c05eb7 100755
--- a/script/release/release.py
+++ b/script/release/release.py
@@ -15,6 +15,7 @@ from release.const import NAME
from release.const import REPO_ROOT
from release.downloader import BinaryDownloader
from release.images import ImageManager
+from release.images import is_tag_latest
from release.pypi import check_pypirc
from release.pypi import pypi_upload
from release.repository import delete_assets
@@ -204,7 +205,7 @@ def resume(args):
delete_assets(gh_release)
upload_assets(gh_release, files)
img_manager = ImageManager(args.release)
- img_manager.build_images(repository, files)
+ img_manager.build_images(repository)
except ScriptError as e:
print(e)
return 1
@@ -244,7 +245,7 @@ def start(args):
gh_release = create_release_draft(repository, args.release, pr_data, files)
upload_assets(gh_release, files)
img_manager = ImageManager(args.release)
- img_manager.build_images(repository, files)
+ img_manager.build_images(repository)
except ScriptError as e:
print(e)
return 1
@@ -258,7 +259,8 @@ def finalize(args):
try:
check_pypirc()
repository = Repository(REPO_ROOT, args.repo)
- img_manager = ImageManager(args.release)
+ tag_as_latest = is_tag_latest(args.release)
+ img_manager = ImageManager(args.release, tag_as_latest)
pr_data = repository.find_release_pr(args.release)
if not pr_data:
raise ScriptError('No PR found for {}'.format(args.release))
diff --git a/script/release/release/const.py b/script/release/release/const.py
index 5a72bde4..52458ea1 100644
--- a/script/release/release/const.py
+++ b/script/release/release/const.py
@@ -6,4 +6,5 @@ import os
REPO_ROOT = os.path.join(os.path.dirname(__file__), '..', '..', '..')
NAME = 'docker/compose'
+COMPOSE_TESTS_IMAGE_BASE_NAME = NAME + '-tests'
BINTRAY_ORG = 'docker-compose'
diff --git a/script/release/release/images.py b/script/release/release/images.py
index df6eeda4..17d572df 100644
--- a/script/release/release/images.py
+++ b/script/release/release/images.py
@@ -5,18 +5,36 @@ from __future__ import unicode_literals
import base64
import json
import os
-import shutil
import docker
+from enum import Enum
+from .const import NAME
from .const import REPO_ROOT
from .utils import ScriptError
+from .utils import yesno
+from script.release.release.const import COMPOSE_TESTS_IMAGE_BASE_NAME
+
+
+class Platform(Enum):
+ ALPINE = 'alpine'
+ DEBIAN = 'debian'
+
+ def __str__(self):
+ return self.value
+
+
+# Checks if this version respects the GA version format ('x.y.z') and not an RC
+def is_tag_latest(version):
+ ga_version = all(n.isdigit() for n in version.split('.')) and version.count('.') == 2
+ return ga_version and yesno('Should this release be tagged as \"latest\"? [Y/n]: ', default=True)
class ImageManager(object):
- def __init__(self, version):
+ def __init__(self, version, latest=False):
self.docker_client = docker.APIClient(**docker.utils.kwargs_from_env())
self.version = version
+ self.latest = latest
if 'HUB_CREDENTIALS' in os.environ:
print('HUB_CREDENTIALS found in environment, issuing login')
credentials = json.loads(base64.urlsafe_b64decode(os.environ['HUB_CREDENTIALS']))
@@ -24,16 +42,36 @@ class ImageManager(object):
username=credentials['Username'], password=credentials['Password']
)
- def build_images(self, repository, files):
- print("Building release images...")
- repository.write_git_sha()
- distdir = os.path.join(REPO_ROOT, 'dist')
- os.makedirs(distdir, exist_ok=True)
- shutil.copy(files['docker-compose-Linux-x86_64'][0], distdir)
- os.chmod(os.path.join(distdir, 'docker-compose-Linux-x86_64'), 0o755)
- print('Building docker/compose image')
+ def _tag(self, image, existing_tag, new_tag):
+ existing_repo_tag = '{image}:{tag}'.format(image=image, tag=existing_tag)
+ new_repo_tag = '{image}:{tag}'.format(image=image, tag=new_tag)
+ self.docker_client.tag(existing_repo_tag, new_repo_tag)
+
+ def get_full_version(self, platform=None):
+ return self.version + '-' + platform.__str__() if platform else self.version
+
+ def get_runtime_image_tag(self, tag):
+ return '{image_base_image}:{tag}'.format(
+ image_base_image=NAME,
+ tag=self.get_full_version(tag)
+ )
+
+ def build_runtime_image(self, repository, platform):
+ git_sha = repository.write_git_sha()
+ compose_image_base_name = NAME
+ print('Building {image} image ({platform} based)'.format(
+ image=compose_image_base_name,
+ platform=platform
+ ))
+ full_version = self.get_full_version(platform)
+ build_tag = self.get_runtime_image_tag(platform)
logstream = self.docker_client.build(
- REPO_ROOT, tag='docker/compose:{}'.format(self.version), dockerfile='Dockerfile.run',
+ REPO_ROOT,
+ tag=build_tag,
+ buildargs={
+ 'BUILD_PLATFORM': platform.value,
+ 'GIT_COMMIT': git_sha,
+ },
decode=True
)
for chunk in logstream:
@@ -42,9 +80,33 @@ class ImageManager(object):
if 'stream' in chunk:
print(chunk['stream'], end='')
- print('Building test image (for UCP e2e)')
+ if platform == Platform.ALPINE:
+ self._tag(compose_image_base_name, full_version, self.version)
+ if self.latest:
+ self._tag(compose_image_base_name, full_version, platform)
+ if platform == Platform.ALPINE:
+ self._tag(compose_image_base_name, full_version, 'latest')
+
+ def get_ucp_test_image_tag(self, tag=None):
+ return '{image}:{tag}'.format(
+ image=COMPOSE_TESTS_IMAGE_BASE_NAME,
+ tag=tag or self.version
+ )
+
+ # Used for producing a test image for UCP
+ def build_ucp_test_image(self, repository):
+ print('Building test image (debian based for UCP e2e)')
+ git_sha = repository.write_git_sha()
+ ucp_test_image_tag = self.get_ucp_test_image_tag()
logstream = self.docker_client.build(
- REPO_ROOT, tag='docker-compose-tests:tmp', decode=True
+ REPO_ROOT,
+ tag=ucp_test_image_tag,
+ target='build',
+ buildargs={
+ 'BUILD_PLATFORM': Platform.DEBIAN.value,
+ 'GIT_COMMIT': git_sha,
+ },
+ decode=True
)
for chunk in logstream:
if 'error' in chunk:
@@ -52,26 +114,15 @@ class ImageManager(object):
if 'stream' in chunk:
print(chunk['stream'], end='')
- container = self.docker_client.create_container(
- 'docker-compose-tests:tmp', entrypoint='tox'
- )
- self.docker_client.commit(container, 'docker/compose-tests', 'latest')
- self.docker_client.tag(
- 'docker/compose-tests:latest', 'docker/compose-tests:{}'.format(self.version)
- )
- self.docker_client.remove_container(container, force=True)
- self.docker_client.remove_image('docker-compose-tests:tmp', force=True)
+ self._tag(COMPOSE_TESTS_IMAGE_BASE_NAME, self.version, 'latest')
- @property
- def image_names(self):
- return [
- 'docker/compose-tests:latest',
- 'docker/compose-tests:{}'.format(self.version),
- 'docker/compose:{}'.format(self.version)
- ]
+ def build_images(self, repository):
+ self.build_runtime_image(repository, Platform.ALPINE)
+ self.build_runtime_image(repository, Platform.DEBIAN)
+ self.build_ucp_test_image(repository)
def check_images(self):
- for name in self.image_names:
+ for name in self.get_images_to_push():
try:
self.docker_client.inspect_image(name)
except docker.errors.ImageNotFound:
@@ -79,8 +130,22 @@ class ImageManager(object):
return False
return True
+ def get_images_to_push(self):
+ tags_to_push = {
+ "{}:{}".format(NAME, self.version),
+ self.get_runtime_image_tag(Platform.ALPINE),
+ self.get_runtime_image_tag(Platform.DEBIAN),
+ self.get_ucp_test_image_tag(),
+ self.get_ucp_test_image_tag('latest'),
+ }
+ if is_tag_latest(self.version):
+ tags_to_push.add("{}:latest".format(NAME))
+ return tags_to_push
+
def push_images(self):
- for name in self.image_names:
+ tags_to_push = self.get_images_to_push()
+ print('Build tags to push {}'.format(tags_to_push))
+ for name in tags_to_push:
print('Pushing {} to Docker Hub'.format(name))
logstream = self.docker_client.push(name, stream=True, decode=True)
for chunk in logstream:
diff --git a/script/release/release/repository.py b/script/release/release/repository.py
index bb8f4fbe..a0281eaa 100644
--- a/script/release/release/repository.py
+++ b/script/release/release/repository.py
@@ -175,6 +175,7 @@ class Repository(object):
def write_git_sha(self):
with open(os.path.join(REPO_ROOT, 'compose', 'GITSHA'), 'w') as f:
f.write(self.git_repo.head.commit.hexsha[:7])
+ return self.git_repo.head.commit.hexsha[:7]
def cherry_pick_prs(self, release_branch, ids):
if not ids:
@@ -219,7 +220,7 @@ def get_contributors(pr_data):
commits = pr_data.get_commits()
authors = {}
for commit in commits:
- if not commit.author:
+ if not commit or not commit.author or not commit.author.login:
continue
author = commit.author.login
authors[author] = authors.get(author, 0) + 1
diff --git a/script/run/run.sh b/script/run/run.sh
index 4881adc3..8756ae34 100755
--- a/script/run/run.sh
+++ b/script/run/run.sh
@@ -15,7 +15,7 @@
set -e
-VERSION="1.24.1"
+VERSION="1.25.0-rc2"
IMAGE="docker/compose:$VERSION"
@@ -48,7 +48,7 @@ fi
# Only allocate tty if we detect one
if [ -t 0 -a -t 1 ]; then
- DOCKER_RUN_OPTIONS="$DOCKER_RUN_OPTIONS -t"
+ DOCKER_RUN_OPTIONS="$DOCKER_RUN_OPTIONS -t"
fi
# Always set -i to support piped and terminal input in run/exec
diff --git a/script/setup/osx b/script/setup/osx
index 1b546816..69280f8a 100755
--- a/script/setup/osx
+++ b/script/setup/osx
@@ -13,13 +13,13 @@ if ! [ ${DEPLOYMENT_TARGET} == "$(macos_version)" ]; then
SDK_SHA1=dd228a335194e3392f1904ce49aff1b1da26ca62
fi
-OPENSSL_VERSION=1.1.0j
+OPENSSL_VERSION=1.1.1c
OPENSSL_URL=https://www.openssl.org/source/openssl-${OPENSSL_VERSION}.tar.gz
-OPENSSL_SHA1=dcad1efbacd9a4ed67d4514470af12bbe2a1d60a
+OPENSSL_SHA1=71b830a077276cbeccc994369538617a21bee808
-PYTHON_VERSION=3.6.8
+PYTHON_VERSION=3.7.4
PYTHON_URL=https://www.python.org/ftp/python/${PYTHON_VERSION}/Python-${PYTHON_VERSION}.tgz
-PYTHON_SHA1=09fcc4edaef0915b4dedbfb462f1cd15f82d3a6f
+PYTHON_SHA1=fb1d764be8a9dcd40f2f152a610a0ab04e0d0ed3
#
# Install prerequisites.
@@ -36,7 +36,7 @@ if ! [ -x "$(command -v python3)" ]; then
brew install python3
fi
if ! [ -x "$(command -v virtualenv)" ]; then
- pip install virtualenv
+ pip install virtualenv==16.2.0
fi
#
@@ -50,7 +50,7 @@ mkdir -p ${TOOLCHAIN_PATH}
#
# Set macOS SDK.
#
-if [ ${SDK_FETCH} ]; then
+if [[ ${SDK_FETCH} && ! -f ${TOOLCHAIN_PATH}/MacOSX${DEPLOYMENT_TARGET}.sdk/SDKSettings.plist ]]; then
SDK_PATH=${TOOLCHAIN_PATH}/MacOSX${DEPLOYMENT_TARGET}.sdk
fetch_tarball ${SDK_URL} ${SDK_PATH} ${SDK_SHA1}
else
@@ -61,7 +61,7 @@ fi
# Build OpenSSL.
#
OPENSSL_SRC_PATH=${TOOLCHAIN_PATH}/openssl-${OPENSSL_VERSION}
-if ! [ -f ${TOOLCHAIN_PATH}/bin/openssl ]; then
+if ! [[ $(${TOOLCHAIN_PATH}/bin/openssl version) == *"${OPENSSL_VERSION}"* ]]; then
rm -rf ${OPENSSL_SRC_PATH}
fetch_tarball ${OPENSSL_URL} ${OPENSSL_SRC_PATH} ${OPENSSL_SHA1}
(
@@ -77,7 +77,7 @@ fi
# Build Python.
#
PYTHON_SRC_PATH=${TOOLCHAIN_PATH}/Python-${PYTHON_VERSION}
-if ! [ -f ${TOOLCHAIN_PATH}/bin/python3 ]; then
+if ! [[ $(${TOOLCHAIN_PATH}/bin/python3 --version) == *"${PYTHON_VERSION}"* ]]; then
rm -rf ${PYTHON_SRC_PATH}
fetch_tarball ${PYTHON_URL} ${PYTHON_SRC_PATH} ${PYTHON_SHA1}
(
@@ -87,9 +87,10 @@ if ! [ -f ${TOOLCHAIN_PATH}/bin/python3 ]; then
--datarootdir=${TOOLCHAIN_PATH}/share \
--datadir=${TOOLCHAIN_PATH}/share \
--enable-framework=${TOOLCHAIN_PATH}/Frameworks \
+ --with-openssl=${TOOLCHAIN_PATH} \
MACOSX_DEPLOYMENT_TARGET=${DEPLOYMENT_TARGET} \
CFLAGS="-isysroot ${SDK_PATH} -I${TOOLCHAIN_PATH}/include" \
- CPPFLAGS="-I${SDK_PATH}/usr/include -I${TOOLCHAIN_PATH}include" \
+ CPPFLAGS="-I${SDK_PATH}/usr/include -I${TOOLCHAIN_PATH}/include" \
LDFLAGS="-isysroot ${SDK_PATH} -L ${TOOLCHAIN_PATH}/lib"
make -j 4
make install PYTHONAPPSDIR=${TOOLCHAIN_PATH}
@@ -97,6 +98,11 @@ if ! [ -f ${TOOLCHAIN_PATH}/bin/python3 ]; then
)
fi
+#
+# Smoke test built Python.
+#
+openssl_version ${TOOLCHAIN_PATH}
+
echo ""
echo "*** Targeting macOS: ${DEPLOYMENT_TARGET}"
echo "*** Using SDK ${SDK_PATH}"
diff --git a/script/test/all b/script/test/all
index e48f73bb..f929a57e 100755
--- a/script/test/all
+++ b/script/test/all
@@ -8,8 +8,7 @@ set -e
docker run --rm \
--tty \
${GIT_VOLUME} \
- --entrypoint="tox" \
- "$TAG" -e pre-commit
+ "$TAG" tox -e pre-commit
get_versions="docker run --rm
--entrypoint=/code/.tox/py27/bin/python
@@ -24,7 +23,7 @@ fi
BUILD_NUMBER=${BUILD_NUMBER-$USER}
-PY_TEST_VERSIONS=${PY_TEST_VERSIONS:-py27,py36}
+PY_TEST_VERSIONS=${PY_TEST_VERSIONS:-py27,py37}
for version in $DOCKER_VERSIONS; do
>&2 echo "Running tests against Docker $version"
diff --git a/script/test/ci b/script/test/ci
index 8d3aa56c..bbcedac4 100755
--- a/script/test/ci
+++ b/script/test/ci
@@ -20,6 +20,3 @@ export DOCKER_DAEMON_ARGS="--storage-driver=$STORAGE_DRIVER"
GIT_VOLUME="--volumes-from=$(hostname)"
. script/test/all
-
->&2 echo "Building Linux binary"
-. script/build/linux-entrypoint
diff --git a/script/test/default b/script/test/default
index cbb6a67c..4f307f2e 100755
--- a/script/test/default
+++ b/script/test/default
@@ -3,17 +3,18 @@
set -ex
-TAG="docker-compose:$(git rev-parse --short HEAD)"
+TAG="docker-compose:alpine-$(git rev-parse --short HEAD)"
# By default use the Dockerfile, but can be overridden to use an alternative file
-# e.g DOCKERFILE=Dockerfile.armhf script/test/default
+# e.g DOCKERFILE=Dockerfile.s390x script/test/default
DOCKERFILE="${DOCKERFILE:-Dockerfile}"
+DOCKER_BUILD_TARGET="${DOCKER_BUILD_TARGET:-build}"
rm -rf coverage-html
# Create the host directory so it's owned by $USER
mkdir -p coverage-html
-docker build -f ${DOCKERFILE} -t "$TAG" .
+docker build -f "${DOCKERFILE}" -t "${TAG}" --target "${DOCKER_BUILD_TARGET}" .
GIT_VOLUME="--volume=$(pwd)/.git:/code/.git"
. script/test/all
diff --git a/setup.py b/setup.py
index 8371cc75..a4020df4 100644
--- a/setup.py
+++ b/setup.py
@@ -31,31 +31,31 @@ def find_version(*file_paths):
install_requires = [
'cached-property >= 1.2.0, < 2',
- 'docopt >= 0.6.1, < 0.7',
- 'PyYAML >= 3.10, < 4.3',
- 'requests >= 2.6.1, != 2.11.0, != 2.12.2, != 2.18.0, < 2.21',
- 'texttable >= 0.9.0, < 0.10',
- 'websocket-client >= 0.32.0, < 1.0',
- 'docker[ssh] >= 3.7.0, < 4.0',
- 'dockerpty >= 0.4.1, < 0.5',
+ 'docopt >= 0.6.1, < 1',
+ 'PyYAML >= 3.10, < 5',
+ 'requests >= 2.20.0, < 3',
+ 'texttable >= 0.9.0, < 2',
+ 'websocket-client >= 0.32.0, < 1',
+ 'docker[ssh] >= 3.7.0, < 5',
+ 'dockerpty >= 0.4.1, < 1',
'six >= 1.3.0, < 2',
'jsonschema >= 2.5.1, < 3',
]
tests_require = [
- 'pytest',
+ 'pytest < 6',
]
if sys.version_info[:2] < (3, 4):
- tests_require.append('mock >= 1.0.1')
+ tests_require.append('mock >= 1.0.1, < 4')
extras_require = {
':python_version < "3.4"': ['enum34 >= 1.0.4, < 2'],
- ':python_version < "3.5"': ['backports.ssl_match_hostname >= 3.5'],
- ':python_version < "3.3"': ['ipaddress >= 1.0.16'],
- ':sys_platform == "win32"': ['colorama >= 0.4, < 0.5'],
+ ':python_version < "3.5"': ['backports.ssl_match_hostname >= 3.5, < 4'],
+ ':python_version < "3.3"': ['ipaddress >= 1.0.16, < 2'],
+ ':sys_platform == "win32"': ['colorama >= 0.4, < 1'],
'socks': ['PySocks >= 1.5.6, != 1.5.7, < 2'],
}
diff --git a/tests/acceptance/cli_test.py b/tests/acceptance/cli_test.py
index 9ed25736..77b46c27 100644
--- a/tests/acceptance/cli_test.py
+++ b/tests/acceptance/cli_test.py
@@ -11,6 +11,7 @@ import subprocess
import time
from collections import Counter
from collections import namedtuple
+from functools import reduce
from operator import attrgetter
import pytest
@@ -19,6 +20,7 @@ import yaml
from docker import errors
from .. import mock
+from ..helpers import BUSYBOX_IMAGE_WITH_TAG
from ..helpers import create_host_file
from compose.cli.command import get_project
from compose.config.errors import DuplicateOverrideFileFound
@@ -62,6 +64,12 @@ def wait_on_process(proc, returncode=0):
return ProcessResult(stdout.decode('utf-8'), stderr.decode('utf-8'))
+def dispatch(base_dir, options, project_options=None, returncode=0):
+ project_options = project_options or []
+ proc = start_process(base_dir, project_options + options)
+ return wait_on_process(proc, returncode=returncode)
+
+
def wait_on_condition(condition, delay=0.1, timeout=40):
start_time = time.time()
while not condition():
@@ -149,9 +157,7 @@ class CLITestCase(DockerClientTestCase):
return self._project
def dispatch(self, options, project_options=None, returncode=0):
- project_options = project_options or []
- proc = start_process(self.base_dir, project_options + options)
- return wait_on_process(proc, returncode=returncode)
+ return dispatch(self.base_dir, options, project_options, returncode)
def execute(self, container, cmd):
# Remove once Hijack and CloseNotifier sign a peace treaty
@@ -170,6 +176,13 @@ class CLITestCase(DockerClientTestCase):
# Prevent tearDown from trying to create a project
self.base_dir = None
+ def test_quiet_build(self):
+ self.base_dir = 'tests/fixtures/build-args'
+ result = self.dispatch(['build'], None)
+ quietResult = self.dispatch(['build', '-q'], None)
+ assert result.stdout != ""
+ assert quietResult.stdout == ""
+
def test_help_nonexistent(self):
self.base_dir = 'tests/fixtures/no-composefile'
result = self.dispatch(['help', 'foobar'], returncode=1)
@@ -258,7 +271,7 @@ class CLITestCase(DockerClientTestCase):
'volumes_from': ['service:other:rw'],
},
'other': {
- 'image': 'busybox:latest',
+ 'image': BUSYBOX_IMAGE_WITH_TAG,
'command': 'top',
'volumes': ['/data'],
},
@@ -324,6 +337,21 @@ class CLITestCase(DockerClientTestCase):
'version': '2.4'
}
+ def test_config_with_env_file(self):
+ self.base_dir = 'tests/fixtures/default-env-file'
+ result = self.dispatch(['--env-file', '.env2', 'config'])
+ json_result = yaml.load(result.stdout)
+ assert json_result == {
+ 'services': {
+ 'web': {
+ 'command': 'false',
+ 'image': 'alpine:latest',
+ 'ports': ['5644/tcp', '9998/tcp']
+ }
+ },
+ 'version': '2.4'
+ }
+
def test_config_with_dot_env_and_override_dir(self):
self.base_dir = 'tests/fixtures/default-env-file'
result = self.dispatch(['--project-directory', 'alt/', 'config'])
@@ -616,7 +644,7 @@ class CLITestCase(DockerClientTestCase):
def test_pull_with_digest(self):
result = self.dispatch(['-f', 'digest.yml', 'pull', '--no-parallel'])
- assert 'Pulling simple (busybox:latest)...' in result.stderr
+ assert 'Pulling simple ({})...'.format(BUSYBOX_IMAGE_WITH_TAG) in result.stderr
assert ('Pulling digest (busybox@'
'sha256:38a203e1986cf79639cfb9b2e1d6e773de84002feea2d4eb006b520'
'04ee8502d)...') in result.stderr
@@ -627,12 +655,19 @@ class CLITestCase(DockerClientTestCase):
'pull', '--ignore-pull-failures', '--no-parallel']
)
- assert 'Pulling simple (busybox:latest)...' in result.stderr
+ assert 'Pulling simple ({})...'.format(BUSYBOX_IMAGE_WITH_TAG) in result.stderr
assert 'Pulling another (nonexisting-image:latest)...' in result.stderr
assert ('repository nonexisting-image not found' in result.stderr or
'image library/nonexisting-image:latest not found' in result.stderr or
'pull access denied for nonexisting-image' in result.stderr)
+ def test_pull_with_build(self):
+ result = self.dispatch(['-f', 'pull-with-build.yml', 'pull'])
+
+ assert 'Pulling simple' not in result.stderr
+ assert 'Pulling from_simple' not in result.stderr
+ assert 'Pulling another ...' in result.stderr
+
def test_pull_with_quiet(self):
assert self.dispatch(['pull', '--quiet']).stderr == ''
assert self.dispatch(['pull', '--quiet']).stdout == ''
@@ -747,6 +782,27 @@ class CLITestCase(DockerClientTestCase):
]
assert not containers
+ @pytest.mark.xfail(True, reason='Flaky on local')
+ def test_build_rm(self):
+ containers = [
+ Container.from_ps(self.project.client, c)
+ for c in self.project.client.containers(all=True)
+ ]
+
+ assert not containers
+
+ self.base_dir = 'tests/fixtures/simple-dockerfile'
+ self.dispatch(['build', '--no-rm', 'simple'], returncode=0)
+
+ containers = [
+ Container.from_ps(self.project.client, c)
+ for c in self.project.client.containers(all=True)
+ ]
+ assert containers
+
+ for c in self.project.client.containers(all=True):
+ self.addCleanup(self.project.client.remove_container, c, force=True)
+
def test_build_shm_size_build_option(self):
pull_busybox(self.client)
self.base_dir = 'tests/fixtures/build-shm-size'
@@ -1109,6 +1165,22 @@ class CLITestCase(DockerClientTestCase):
assert len(remote_volumes) > 0
@v2_only()
+ def test_up_no_start_remove_orphans(self):
+ self.base_dir = 'tests/fixtures/v2-simple'
+ self.dispatch(['up', '--no-start'], None)
+
+ services = self.project.get_services()
+
+ stopped = reduce((lambda prev, next: prev.containers(
+ stopped=True) + next.containers(stopped=True)), services)
+ assert len(stopped) == 2
+
+ self.dispatch(['-f', 'one-container.yml', 'up', '--no-start', '--remove-orphans'], None)
+ stopped2 = reduce((lambda prev, next: prev.containers(
+ stopped=True) + next.containers(stopped=True)), services)
+ assert len(stopped2) == 1
+
+ @v2_only()
def test_up_no_ansi(self):
self.base_dir = 'tests/fixtures/v2-simple'
result = self.dispatch(['--no-ansi', 'up', '-d'], None)
@@ -1380,7 +1452,7 @@ class CLITestCase(DockerClientTestCase):
if v['Name'].split('/')[-1].startswith('{}_'.format(self.project.name))
]
- assert set([v['Name'].split('/')[-1] for v in volumes]) == set([volume_with_label])
+ assert set([v['Name'].split('/')[-1] for v in volumes]) == {volume_with_label}
assert 'label_key' in volumes[0]['Labels']
assert volumes[0]['Labels']['label_key'] == 'label_val'
@@ -2045,7 +2117,7 @@ class CLITestCase(DockerClientTestCase):
for _, config in networks.items():
# TODO: once we drop support for API <1.24, this can be changed to:
# assert config['Aliases'] == [container.short_id]
- aliases = set(config['Aliases'] or []) - set([container.short_id])
+ aliases = set(config['Aliases'] or []) - {container.short_id}
assert not aliases
@v2_only()
@@ -2065,7 +2137,7 @@ class CLITestCase(DockerClientTestCase):
for _, config in networks.items():
# TODO: once we drop support for API <1.24, this can be changed to:
# assert config['Aliases'] == [container.short_id]
- aliases = set(config['Aliases'] or []) - set([container.short_id])
+ aliases = set(config['Aliases'] or []) - {container.short_id}
assert not aliases
assert self.lookup(container, 'app')
@@ -2301,6 +2373,7 @@ class CLITestCase(DockerClientTestCase):
assert 'another' in result.stdout
assert 'exited with code 0' in result.stdout
+ @pytest.mark.skip(reason="race condition between up and logs")
def test_logs_follow_logs_from_new_containers(self):
self.base_dir = 'tests/fixtures/logs-composefile'
self.dispatch(['up', '-d', 'simple'])
@@ -2327,6 +2400,7 @@ class CLITestCase(DockerClientTestCase):
assert '{} exited with code 0'.format(another_name) in result.stdout
assert '{} exited with code 137'.format(simple_name) in result.stdout
+ @pytest.mark.skip(reason="race condition between up and logs")
def test_logs_follow_logs_from_restarted_containers(self):
self.base_dir = 'tests/fixtures/logs-restart-composefile'
proc = start_process(self.base_dir, ['up'])
@@ -2347,6 +2421,7 @@ class CLITestCase(DockerClientTestCase):
) == 3
assert result.stdout.count('world') == 3
+ @pytest.mark.skip(reason="race condition between up and logs")
def test_logs_default(self):
self.base_dir = 'tests/fixtures/logs-composefile'
self.dispatch(['up', '-d'])
@@ -2473,10 +2548,12 @@ class CLITestCase(DockerClientTestCase):
self.dispatch(['up', '-d'])
assert len(project.get_service('web').containers()) == 2
assert len(project.get_service('db').containers()) == 1
+ assert len(project.get_service('worker').containers()) == 0
- self.dispatch(['up', '-d', '--scale', 'web=3'])
+ self.dispatch(['up', '-d', '--scale', 'web=3', '--scale', 'worker=1'])
assert len(project.get_service('web').containers()) == 3
assert len(project.get_service('db').containers()) == 1
+ assert len(project.get_service('worker').containers()) == 1
def test_up_scale_scale_down(self):
self.base_dir = 'tests/fixtures/scale'
@@ -2485,22 +2562,26 @@ class CLITestCase(DockerClientTestCase):
self.dispatch(['up', '-d'])
assert len(project.get_service('web').containers()) == 2
assert len(project.get_service('db').containers()) == 1
+ assert len(project.get_service('worker').containers()) == 0
self.dispatch(['up', '-d', '--scale', 'web=1'])
assert len(project.get_service('web').containers()) == 1
assert len(project.get_service('db').containers()) == 1
+ assert len(project.get_service('worker').containers()) == 0
def test_up_scale_reset(self):
self.base_dir = 'tests/fixtures/scale'
project = self.project
- self.dispatch(['up', '-d', '--scale', 'web=3', '--scale', 'db=3'])
+ self.dispatch(['up', '-d', '--scale', 'web=3', '--scale', 'db=3', '--scale', 'worker=3'])
assert len(project.get_service('web').containers()) == 3
assert len(project.get_service('db').containers()) == 3
+ assert len(project.get_service('worker').containers()) == 3
self.dispatch(['up', '-d'])
assert len(project.get_service('web').containers()) == 2
assert len(project.get_service('db').containers()) == 1
+ assert len(project.get_service('worker').containers()) == 0
def test_up_scale_to_zero(self):
self.base_dir = 'tests/fixtures/scale'
@@ -2509,10 +2590,12 @@ class CLITestCase(DockerClientTestCase):
self.dispatch(['up', '-d'])
assert len(project.get_service('web').containers()) == 2
assert len(project.get_service('db').containers()) == 1
+ assert len(project.get_service('worker').containers()) == 0
- self.dispatch(['up', '-d', '--scale', 'web=0', '--scale', 'db=0'])
+ self.dispatch(['up', '-d', '--scale', 'web=0', '--scale', 'db=0', '--scale', 'worker=0'])
assert len(project.get_service('web').containers()) == 0
assert len(project.get_service('db').containers()) == 0
+ assert len(project.get_service('worker').containers()) == 0
def test_port(self):
self.base_dir = 'tests/fixtures/ports-composefile'
@@ -2664,7 +2747,7 @@ class CLITestCase(DockerClientTestCase):
self.base_dir = 'tests/fixtures/extends'
self.dispatch(['up', '-d'], None)
- assert set([s.name for s in self.project.services]) == set(['mydb', 'myweb'])
+ assert set([s.name for s in self.project.services]) == {'mydb', 'myweb'}
# Sort by name so we get [db, web]
containers = sorted(
@@ -2676,15 +2759,9 @@ class CLITestCase(DockerClientTestCase):
web = containers[1]
db_name = containers[0].name_without_project
- assert set(get_links(web)) == set(
- ['db', db_name, 'extends_{}'.format(db_name)]
- )
+ assert set(get_links(web)) == {'db', db_name, 'extends_{}'.format(db_name)}
- expected_env = set([
- "FOO=1",
- "BAR=2",
- "BAZ=2",
- ])
+ expected_env = {"FOO=1", "BAR=2", "BAZ=2"}
assert expected_env <= set(web.get('Config.Env'))
def test_top_services_not_running(self):
diff --git a/tests/fixtures/UpperCaseDir/docker-compose.yml b/tests/fixtures/UpperCaseDir/docker-compose.yml
index b25beaf4..09cc9519 100644
--- a/tests/fixtures/UpperCaseDir/docker-compose.yml
+++ b/tests/fixtures/UpperCaseDir/docker-compose.yml
@@ -1,6 +1,6 @@
simple:
- image: busybox:latest
+ image: busybox:1.31.0-uclibc
command: top
another:
- image: busybox:latest
+ image: busybox:1.31.0-uclibc
command: top
diff --git a/tests/fixtures/abort-on-container-exit-0/docker-compose.yml b/tests/fixtures/abort-on-container-exit-0/docker-compose.yml
index ce41697b..77307ef2 100644
--- a/tests/fixtures/abort-on-container-exit-0/docker-compose.yml
+++ b/tests/fixtures/abort-on-container-exit-0/docker-compose.yml
@@ -1,6 +1,6 @@
simple:
- image: busybox:latest
+ image: busybox:1.31.0-uclibc
command: top
another:
- image: busybox:latest
+ image: busybox:1.31.0-uclibc
command: ls .
diff --git a/tests/fixtures/abort-on-container-exit-1/docker-compose.yml b/tests/fixtures/abort-on-container-exit-1/docker-compose.yml
index 7ec9b7e1..23290964 100644
--- a/tests/fixtures/abort-on-container-exit-1/docker-compose.yml
+++ b/tests/fixtures/abort-on-container-exit-1/docker-compose.yml
@@ -1,6 +1,6 @@
simple:
- image: busybox:latest
+ image: busybox:1.31.0-uclibc
command: top
another:
- image: busybox:latest
+ image: busybox:1.31.0-uclibc
command: ls /thecakeisalie
diff --git a/tests/fixtures/build-args/Dockerfile b/tests/fixtures/build-args/Dockerfile
index 93ebcb9c..d1534068 100644
--- a/tests/fixtures/build-args/Dockerfile
+++ b/tests/fixtures/build-args/Dockerfile
@@ -1,4 +1,4 @@
-FROM busybox:latest
+FROM busybox:1.31.0-uclibc
LABEL com.docker.compose.test_image=true
ARG favorite_th_character
RUN echo "Favorite Touhou Character: ${favorite_th_character}"
diff --git a/tests/fixtures/build-ctx/Dockerfile b/tests/fixtures/build-ctx/Dockerfile
index dd864b83..4acac9c7 100644
--- a/tests/fixtures/build-ctx/Dockerfile
+++ b/tests/fixtures/build-ctx/Dockerfile
@@ -1,3 +1,3 @@
-FROM busybox:latest
+FROM busybox:1.31.0-uclibc
LABEL com.docker.compose.test_image=true
CMD echo "success"
diff --git a/tests/fixtures/build-memory/Dockerfile b/tests/fixtures/build-memory/Dockerfile
index b27349b9..076b84d7 100644
--- a/tests/fixtures/build-memory/Dockerfile
+++ b/tests/fixtures/build-memory/Dockerfile
@@ -1,4 +1,4 @@
-FROM busybox
+FROM busybox:1.31.0-uclibc
# Report the memory (through the size of the group memory)
RUN echo "memory:" $(cat /sys/fs/cgroup/memory/memory.limit_in_bytes)
diff --git a/tests/fixtures/build-multiple-composefile/a/Dockerfile b/tests/fixtures/build-multiple-composefile/a/Dockerfile
index 2ba45ce5..52ed15ec 100644
--- a/tests/fixtures/build-multiple-composefile/a/Dockerfile
+++ b/tests/fixtures/build-multiple-composefile/a/Dockerfile
@@ -1,4 +1,4 @@
-FROM busybox:latest
+FROM busybox:1.31.0-uclibc
RUN echo a
CMD top
diff --git a/tests/fixtures/build-multiple-composefile/b/Dockerfile b/tests/fixtures/build-multiple-composefile/b/Dockerfile
index e282e8bb..932d851d 100644
--- a/tests/fixtures/build-multiple-composefile/b/Dockerfile
+++ b/tests/fixtures/build-multiple-composefile/b/Dockerfile
@@ -1,4 +1,4 @@
-FROM busybox:latest
+FROM busybox:1.31.0-uclibc
RUN echo b
CMD top
diff --git a/tests/fixtures/default-env-file/.env2 b/tests/fixtures/default-env-file/.env2
new file mode 100644
index 00000000..d754523f
--- /dev/null
+++ b/tests/fixtures/default-env-file/.env2
@@ -0,0 +1,4 @@
+IMAGE=alpine:latest
+COMMAND=false
+PORT1=5644
+PORT2=9998
diff --git a/tests/fixtures/dockerfile-with-volume/Dockerfile b/tests/fixtures/dockerfile-with-volume/Dockerfile
index 0d376ec4..f38e1d57 100644
--- a/tests/fixtures/dockerfile-with-volume/Dockerfile
+++ b/tests/fixtures/dockerfile-with-volume/Dockerfile
@@ -1,4 +1,4 @@
-FROM busybox:latest
+FROM busybox:1.31.0-uclibc
LABEL com.docker.compose.test_image=true
VOLUME /data
CMD top
diff --git a/tests/fixtures/duplicate-override-yaml-files/docker-compose.yml b/tests/fixtures/duplicate-override-yaml-files/docker-compose.yml
index 5f2909d6..6880435b 100644
--- a/tests/fixtures/duplicate-override-yaml-files/docker-compose.yml
+++ b/tests/fixtures/duplicate-override-yaml-files/docker-compose.yml
@@ -1,10 +1,10 @@
web:
- image: busybox:latest
+ image: busybox:1.31.0-uclibc
command: "sleep 100"
links:
- db
db:
- image: busybox:latest
+ image: busybox:1.31.0-uclibc
command: "sleep 200"
diff --git a/tests/fixtures/echo-services/docker-compose.yml b/tests/fixtures/echo-services/docker-compose.yml
index 8014f3d9..75fc45d9 100644
--- a/tests/fixtures/echo-services/docker-compose.yml
+++ b/tests/fixtures/echo-services/docker-compose.yml
@@ -1,6 +1,6 @@
simple:
- image: busybox:latest
+ image: busybox:1.31.0-uclibc
command: echo simple
another:
- image: busybox:latest
+ image: busybox:1.31.0-uclibc
command: echo another
diff --git a/tests/fixtures/entrypoint-dockerfile/Dockerfile b/tests/fixtures/entrypoint-dockerfile/Dockerfile
index 49f4416c..30ec50ba 100644
--- a/tests/fixtures/entrypoint-dockerfile/Dockerfile
+++ b/tests/fixtures/entrypoint-dockerfile/Dockerfile
@@ -1,4 +1,4 @@
-FROM busybox:latest
+FROM busybox:1.31.0-uclibc
LABEL com.docker.compose.test_image=true
ENTRYPOINT ["printf"]
CMD ["default", "args"]
diff --git a/tests/fixtures/env-file-override/.env.conf b/tests/fixtures/env-file-override/.env.conf
new file mode 100644
index 00000000..90b8b495
--- /dev/null
+++ b/tests/fixtures/env-file-override/.env.conf
@@ -0,0 +1,2 @@
+WHEREAMI
+DEFAULT_CONF_LOADED=true
diff --git a/tests/fixtures/env-file-override/.env.override b/tests/fixtures/env-file-override/.env.override
new file mode 100644
index 00000000..398fa51b
--- /dev/null
+++ b/tests/fixtures/env-file-override/.env.override
@@ -0,0 +1 @@
+WHEREAMI=override
diff --git a/tests/fixtures/env-file-override/docker-compose.yml b/tests/fixtures/env-file-override/docker-compose.yml
new file mode 100644
index 00000000..fdae6d82
--- /dev/null
+++ b/tests/fixtures/env-file-override/docker-compose.yml
@@ -0,0 +1,6 @@
+version: '3.7'
+services:
+ test:
+ image: busybox
+ env_file: .env.conf
+ entrypoint: env
diff --git a/tests/fixtures/environment-composefile/docker-compose.yml b/tests/fixtures/environment-composefile/docker-compose.yml
index 9d99fee0..5650c7c8 100644
--- a/tests/fixtures/environment-composefile/docker-compose.yml
+++ b/tests/fixtures/environment-composefile/docker-compose.yml
@@ -1,5 +1,5 @@
service:
- image: busybox:latest
+ image: busybox:1.31.0-uclibc
command: top
environment:
diff --git a/tests/fixtures/exit-code-from/docker-compose.yml b/tests/fixtures/exit-code-from/docker-compose.yml
index 687e78b9..c38bd549 100644
--- a/tests/fixtures/exit-code-from/docker-compose.yml
+++ b/tests/fixtures/exit-code-from/docker-compose.yml
@@ -1,6 +1,6 @@
simple:
- image: busybox:latest
+ image: busybox:1.31.0-uclibc
command: sh -c "echo hello && tail -f /dev/null"
another:
- image: busybox:latest
+ image: busybox:1.31.0-uclibc
command: /bin/false
diff --git a/tests/fixtures/expose-composefile/docker-compose.yml b/tests/fixtures/expose-composefile/docker-compose.yml
index d14a468d..c2a3dc42 100644
--- a/tests/fixtures/expose-composefile/docker-compose.yml
+++ b/tests/fixtures/expose-composefile/docker-compose.yml
@@ -1,6 +1,6 @@
simple:
- image: busybox:latest
+ image: busybox:1.31.0-uclibc
command: top
expose:
- '3000'
diff --git a/tests/fixtures/images-service-tag/Dockerfile b/tests/fixtures/images-service-tag/Dockerfile
index 145e0202..1e1a1b2e 100644
--- a/tests/fixtures/images-service-tag/Dockerfile
+++ b/tests/fixtures/images-service-tag/Dockerfile
@@ -1,2 +1,2 @@
-FROM busybox:latest
+FROM busybox:1.31.0-uclibc
RUN touch /foo
diff --git a/tests/fixtures/logging-composefile-legacy/docker-compose.yml b/tests/fixtures/logging-composefile-legacy/docker-compose.yml
index ee994107..efac1d6a 100644
--- a/tests/fixtures/logging-composefile-legacy/docker-compose.yml
+++ b/tests/fixtures/logging-composefile-legacy/docker-compose.yml
@@ -1,9 +1,9 @@
simple:
- image: busybox:latest
+ image: busybox:1.31.0-uclibc
command: top
log_driver: "none"
another:
- image: busybox:latest
+ image: busybox:1.31.0-uclibc
command: top
log_driver: "json-file"
log_opt:
diff --git a/tests/fixtures/logging-composefile/docker-compose.yml b/tests/fixtures/logging-composefile/docker-compose.yml
index 466d13e5..ac231b89 100644
--- a/tests/fixtures/logging-composefile/docker-compose.yml
+++ b/tests/fixtures/logging-composefile/docker-compose.yml
@@ -1,12 +1,12 @@
version: "2"
services:
simple:
- image: busybox:latest
+ image: busybox:1.31.0-uclibc
command: top
logging:
driver: "none"
another:
- image: busybox:latest
+ image: busybox:1.31.0-uclibc
command: top
logging:
driver: "json-file"
diff --git a/tests/fixtures/logs-composefile/docker-compose.yml b/tests/fixtures/logs-composefile/docker-compose.yml
index b719c91e..3ffaa984 100644
--- a/tests/fixtures/logs-composefile/docker-compose.yml
+++ b/tests/fixtures/logs-composefile/docker-compose.yml
@@ -1,6 +1,6 @@
simple:
- image: busybox:latest
- command: sh -c "echo hello && tail -f /dev/null"
+ image: busybox:1.31.0-uclibc
+ command: sh -c "sleep 1 && echo hello && tail -f /dev/null"
another:
- image: busybox:latest
- command: sh -c "echo test"
+ image: busybox:1.31.0-uclibc
+ command: sh -c "sleep 1 && echo test"
diff --git a/tests/fixtures/logs-restart-composefile/docker-compose.yml b/tests/fixtures/logs-restart-composefile/docker-compose.yml
index c662a1e7..2179d54d 100644
--- a/tests/fixtures/logs-restart-composefile/docker-compose.yml
+++ b/tests/fixtures/logs-restart-composefile/docker-compose.yml
@@ -1,7 +1,7 @@
simple:
- image: busybox:latest
+ image: busybox:1.31.0-uclibc
command: sh -c "echo hello && tail -f /dev/null"
another:
- image: busybox:latest
- command: sh -c "sleep 0.5 && echo world && /bin/false"
+ image: busybox:1.31.0-uclibc
+ command: sh -c "sleep 2 && echo world && /bin/false"
restart: "on-failure:2"
diff --git a/tests/fixtures/logs-tail-composefile/docker-compose.yml b/tests/fixtures/logs-tail-composefile/docker-compose.yml
index b70d0cc6..18dad986 100644
--- a/tests/fixtures/logs-tail-composefile/docker-compose.yml
+++ b/tests/fixtures/logs-tail-composefile/docker-compose.yml
@@ -1,3 +1,3 @@
simple:
- image: busybox:latest
+ image: busybox:1.31.0-uclibc
command: sh -c "echo w && echo x && echo y && echo z"
diff --git a/tests/fixtures/longer-filename-composefile/docker-compose.yaml b/tests/fixtures/longer-filename-composefile/docker-compose.yaml
index a4eba2d0..5dadce44 100644
--- a/tests/fixtures/longer-filename-composefile/docker-compose.yaml
+++ b/tests/fixtures/longer-filename-composefile/docker-compose.yaml
@@ -1,3 +1,3 @@
definedinyamlnotyml:
- image: busybox:latest
+ image: busybox:1.31.0-uclibc
command: top
diff --git a/tests/fixtures/multiple-composefiles/compose2.yml b/tests/fixtures/multiple-composefiles/compose2.yml
index 56803380..530d92df 100644
--- a/tests/fixtures/multiple-composefiles/compose2.yml
+++ b/tests/fixtures/multiple-composefiles/compose2.yml
@@ -1,3 +1,3 @@
yetanother:
- image: busybox:latest
+ image: busybox:1.31.0-uclibc
command: top
diff --git a/tests/fixtures/multiple-composefiles/docker-compose.yml b/tests/fixtures/multiple-composefiles/docker-compose.yml
index b25beaf4..09cc9519 100644
--- a/tests/fixtures/multiple-composefiles/docker-compose.yml
+++ b/tests/fixtures/multiple-composefiles/docker-compose.yml
@@ -1,6 +1,6 @@
simple:
- image: busybox:latest
+ image: busybox:1.31.0-uclibc
command: top
another:
- image: busybox:latest
+ image: busybox:1.31.0-uclibc
command: top
diff --git a/tests/fixtures/networks/default-network-config.yml b/tests/fixtures/networks/default-network-config.yml
index 4bd0989b..556ca980 100644
--- a/tests/fixtures/networks/default-network-config.yml
+++ b/tests/fixtures/networks/default-network-config.yml
@@ -1,10 +1,10 @@
version: "2"
services:
simple:
- image: busybox:latest
+ image: busybox:1.31.0-uclibc
command: top
another:
- image: busybox:latest
+ image: busybox:1.31.0-uclibc
command: top
networks:
default:
diff --git a/tests/fixtures/networks/external-default.yml b/tests/fixtures/networks/external-default.yml
index 5c9426b8..42a39565 100644
--- a/tests/fixtures/networks/external-default.yml
+++ b/tests/fixtures/networks/external-default.yml
@@ -1,10 +1,10 @@
version: "2"
services:
simple:
- image: busybox:latest
+ image: busybox:1.31.0-uclibc
command: top
another:
- image: busybox:latest
+ image: busybox:1.31.0-uclibc
command: top
networks:
default:
diff --git a/tests/fixtures/no-links-composefile/docker-compose.yml b/tests/fixtures/no-links-composefile/docker-compose.yml
index 75a6a085..54936f30 100644
--- a/tests/fixtures/no-links-composefile/docker-compose.yml
+++ b/tests/fixtures/no-links-composefile/docker-compose.yml
@@ -1,9 +1,9 @@
db:
- image: busybox:latest
+ image: busybox:1.31.0-uclibc
command: top
web:
- image: busybox:latest
+ image: busybox:1.31.0-uclibc
command: top
console:
- image: busybox:latest
+ image: busybox:1.31.0-uclibc
command: top
diff --git a/tests/fixtures/override-files/docker-compose.yml b/tests/fixtures/override-files/docker-compose.yml
index 6c3d4e17..0119ec73 100644
--- a/tests/fixtures/override-files/docker-compose.yml
+++ b/tests/fixtures/override-files/docker-compose.yml
@@ -1,10 +1,10 @@
version: '2.2'
services:
web:
- image: busybox:latest
+ image: busybox:1.31.0-uclibc
command: "sleep 200"
depends_on:
- db
db:
- image: busybox:latest
+ image: busybox:1.31.0-uclibc
command: "sleep 200"
diff --git a/tests/fixtures/override-files/extra.yml b/tests/fixtures/override-files/extra.yml
index 492c3795..d03c5096 100644
--- a/tests/fixtures/override-files/extra.yml
+++ b/tests/fixtures/override-files/extra.yml
@@ -6,5 +6,5 @@ services:
- other
other:
- image: busybox:latest
+ image: busybox:1.31.0-uclibc
command: "top"
diff --git a/tests/fixtures/override-yaml-files/docker-compose.yml b/tests/fixtures/override-yaml-files/docker-compose.yml
index 5f2909d6..6880435b 100644
--- a/tests/fixtures/override-yaml-files/docker-compose.yml
+++ b/tests/fixtures/override-yaml-files/docker-compose.yml
@@ -1,10 +1,10 @@
web:
- image: busybox:latest
+ image: busybox:1.31.0-uclibc
command: "sleep 100"
links:
- db
db:
- image: busybox:latest
+ image: busybox:1.31.0-uclibc
command: "sleep 200"
diff --git a/tests/fixtures/ports-composefile-scale/docker-compose.yml b/tests/fixtures/ports-composefile-scale/docker-compose.yml
index 1a2bb485..bdd39cef 100644
--- a/tests/fixtures/ports-composefile-scale/docker-compose.yml
+++ b/tests/fixtures/ports-composefile-scale/docker-compose.yml
@@ -1,6 +1,6 @@
simple:
- image: busybox:latest
+ image: busybox:1.31.0-uclibc
command: /bin/sleep 300
ports:
- '3000'
diff --git a/tests/fixtures/ports-composefile/docker-compose.yml b/tests/fixtures/ports-composefile/docker-compose.yml
index c213068d..f4987027 100644
--- a/tests/fixtures/ports-composefile/docker-compose.yml
+++ b/tests/fixtures/ports-composefile/docker-compose.yml
@@ -1,6 +1,6 @@
simple:
- image: busybox:latest
+ image: busybox:1.31.0-uclibc
command: top
ports:
- '3000'
diff --git a/tests/fixtures/ports-composefile/expanded-notation.yml b/tests/fixtures/ports-composefile/expanded-notation.yml
index 09a7a2bf..6510e428 100644
--- a/tests/fixtures/ports-composefile/expanded-notation.yml
+++ b/tests/fixtures/ports-composefile/expanded-notation.yml
@@ -1,7 +1,7 @@
version: '3.2'
services:
simple:
- image: busybox:latest
+ image: busybox:1.31.0-uclibc
command: top
ports:
- target: 3000
diff --git a/tests/fixtures/ps-services-filter/docker-compose.yml b/tests/fixtures/ps-services-filter/docker-compose.yml
index 3d860937..180f515a 100644
--- a/tests/fixtures/ps-services-filter/docker-compose.yml
+++ b/tests/fixtures/ps-services-filter/docker-compose.yml
@@ -1,5 +1,5 @@
with_image:
- image: busybox:latest
+ image: busybox:1.31.0-uclibc
command: top
with_build:
build: ../build-ctx/
diff --git a/tests/fixtures/run-labels/docker-compose.yml b/tests/fixtures/run-labels/docker-compose.yml
index e8cd5006..e3b237fd 100644
--- a/tests/fixtures/run-labels/docker-compose.yml
+++ b/tests/fixtures/run-labels/docker-compose.yml
@@ -1,5 +1,5 @@
service:
- image: busybox:latest
+ image: busybox:1.31.0-uclibc
command: top
labels:
diff --git a/tests/fixtures/run-workdir/docker-compose.yml b/tests/fixtures/run-workdir/docker-compose.yml
index dc3ea86a..9d092a55 100644
--- a/tests/fixtures/run-workdir/docker-compose.yml
+++ b/tests/fixtures/run-workdir/docker-compose.yml
@@ -1,4 +1,4 @@
service:
- image: busybox:latest
+ image: busybox:1.31.0-uclibc
working_dir: /etc
command: /bin/true
diff --git a/tests/fixtures/scale/docker-compose.yml b/tests/fixtures/scale/docker-compose.yml
index a0d3b771..53ae1342 100644
--- a/tests/fixtures/scale/docker-compose.yml
+++ b/tests/fixtures/scale/docker-compose.yml
@@ -5,5 +5,9 @@ services:
command: top
scale: 2
db:
- image: busybox
- command: top
+ image: busybox
+ command: top
+ worker:
+ image: busybox
+ command: top
+ scale: 0
diff --git a/tests/fixtures/simple-composefile-volume-ready/docker-compose.merge.yml b/tests/fixtures/simple-composefile-volume-ready/docker-compose.merge.yml
index fe717151..45b626d0 100644
--- a/tests/fixtures/simple-composefile-volume-ready/docker-compose.merge.yml
+++ b/tests/fixtures/simple-composefile-volume-ready/docker-compose.merge.yml
@@ -1,7 +1,7 @@
version: '2.2'
services:
simple:
- image: busybox:latest
+ image: busybox:1.31.0-uclibc
volumes:
- datastore:/data1
diff --git a/tests/fixtures/simple-composefile-volume-ready/docker-compose.yml b/tests/fixtures/simple-composefile-volume-ready/docker-compose.yml
index 98a7d23b..088d71c9 100644
--- a/tests/fixtures/simple-composefile-volume-ready/docker-compose.yml
+++ b/tests/fixtures/simple-composefile-volume-ready/docker-compose.yml
@@ -1,2 +1,2 @@
simple:
- image: busybox:latest
+ image: busybox:1.31.0-uclibc
diff --git a/tests/fixtures/simple-composefile/digest.yml b/tests/fixtures/simple-composefile/digest.yml
index 08f1d993..79f043ba 100644
--- a/tests/fixtures/simple-composefile/digest.yml
+++ b/tests/fixtures/simple-composefile/digest.yml
@@ -1,5 +1,5 @@
simple:
- image: busybox:latest
+ image: busybox:1.31.0-uclibc
command: top
digest:
image: busybox@sha256:38a203e1986cf79639cfb9b2e1d6e773de84002feea2d4eb006b52004ee8502d
diff --git a/tests/fixtures/simple-composefile/docker-compose.yml b/tests/fixtures/simple-composefile/docker-compose.yml
index e86d3fc8..b66a0652 100644
--- a/tests/fixtures/simple-composefile/docker-compose.yml
+++ b/tests/fixtures/simple-composefile/docker-compose.yml
@@ -2,5 +2,5 @@ simple:
image: busybox:1.27.2
command: top
another:
- image: busybox:latest
+ image: busybox:1.31.0-uclibc
command: top
diff --git a/tests/fixtures/simple-composefile/ignore-pull-failures.yml b/tests/fixtures/simple-composefile/ignore-pull-failures.yml
index a28f7922..7e7d560d 100644
--- a/tests/fixtures/simple-composefile/ignore-pull-failures.yml
+++ b/tests/fixtures/simple-composefile/ignore-pull-failures.yml
@@ -1,5 +1,5 @@
simple:
- image: busybox:latest
+ image: busybox:1.31.0-uclibc
command: top
another:
image: nonexisting-image:latest
diff --git a/tests/fixtures/simple-composefile/pull-with-build.yml b/tests/fixtures/simple-composefile/pull-with-build.yml
new file mode 100644
index 00000000..3bff35c5
--- /dev/null
+++ b/tests/fixtures/simple-composefile/pull-with-build.yml
@@ -0,0 +1,11 @@
+version: "3"
+services:
+ build_simple:
+ image: simple
+ build: .
+ command: top
+ from_simple:
+ image: simple
+ another:
+ image: busybox:1.31.0-uclibc
+ command: top
diff --git a/tests/fixtures/simple-failing-dockerfile/Dockerfile b/tests/fixtures/simple-failing-dockerfile/Dockerfile
index c2d06b16..205021a2 100644
--- a/tests/fixtures/simple-failing-dockerfile/Dockerfile
+++ b/tests/fixtures/simple-failing-dockerfile/Dockerfile
@@ -1,4 +1,4 @@
-FROM busybox:latest
+FROM busybox:1.31.0-uclibc
LABEL com.docker.compose.test_image=true
LABEL com.docker.compose.test_failing_image=true
# With the following label the container wil be cleaned up automatically
diff --git a/tests/fixtures/sleeps-composefile/docker-compose.yml b/tests/fixtures/sleeps-composefile/docker-compose.yml
index 7c8d84f8..26feb502 100644
--- a/tests/fixtures/sleeps-composefile/docker-compose.yml
+++ b/tests/fixtures/sleeps-composefile/docker-compose.yml
@@ -3,8 +3,8 @@ version: "2"
services:
simple:
- image: busybox:latest
+ image: busybox:1.31.0-uclibc
command: sleep 200
another:
- image: busybox:latest
+ image: busybox:1.31.0-uclibc
command: sleep 200
diff --git a/tests/fixtures/stop-signal-composefile/docker-compose.yml b/tests/fixtures/stop-signal-composefile/docker-compose.yml
index 04f58aa9..9f99b0c7 100644
--- a/tests/fixtures/stop-signal-composefile/docker-compose.yml
+++ b/tests/fixtures/stop-signal-composefile/docker-compose.yml
@@ -1,5 +1,5 @@
simple:
- image: busybox:latest
+ image: busybox:1.31.0-uclibc
command:
- sh
- '-c'
diff --git a/tests/fixtures/tagless-image/Dockerfile b/tests/fixtures/tagless-image/Dockerfile
index 56741055..92305555 100644
--- a/tests/fixtures/tagless-image/Dockerfile
+++ b/tests/fixtures/tagless-image/Dockerfile
@@ -1,2 +1,2 @@
-FROM busybox:latest
+FROM busybox:1.31.0-uclibc
RUN touch /blah
diff --git a/tests/fixtures/top/docker-compose.yml b/tests/fixtures/top/docker-compose.yml
index d632a836..36a3917d 100644
--- a/tests/fixtures/top/docker-compose.yml
+++ b/tests/fixtures/top/docker-compose.yml
@@ -1,6 +1,6 @@
service_a:
- image: busybox:latest
+ image: busybox:1.31.0-uclibc
command: top
service_b:
- image: busybox:latest
+ image: busybox:1.31.0-uclibc
command: top
diff --git a/tests/fixtures/unicode-environment/docker-compose.yml b/tests/fixtures/unicode-environment/docker-compose.yml
index a41af4f0..307678cd 100644
--- a/tests/fixtures/unicode-environment/docker-compose.yml
+++ b/tests/fixtures/unicode-environment/docker-compose.yml
@@ -1,7 +1,7 @@
version: '2'
services:
simple:
- image: busybox:latest
+ image: busybox:1.31.0-uclibc
command: sh -c 'echo $$FOO'
environment:
FOO: ${BAR}
diff --git a/tests/fixtures/user-composefile/docker-compose.yml b/tests/fixtures/user-composefile/docker-compose.yml
index 3eb7d397..11283d9d 100644
--- a/tests/fixtures/user-composefile/docker-compose.yml
+++ b/tests/fixtures/user-composefile/docker-compose.yml
@@ -1,4 +1,4 @@
service:
- image: busybox:latest
+ image: busybox:1.31.0-uclibc
user: notauser
command: id
diff --git a/tests/fixtures/v2-dependencies/docker-compose.yml b/tests/fixtures/v2-dependencies/docker-compose.yml
index 2e14b94b..45ec8501 100644
--- a/tests/fixtures/v2-dependencies/docker-compose.yml
+++ b/tests/fixtures/v2-dependencies/docker-compose.yml
@@ -1,13 +1,13 @@
version: "2.0"
services:
db:
- image: busybox:latest
+ image: busybox:1.31.0-uclibc
command: top
web:
- image: busybox:latest
+ image: busybox:1.31.0-uclibc
command: top
depends_on:
- db
console:
- image: busybox:latest
+ image: busybox:1.31.0-uclibc
command: top
diff --git a/tests/fixtures/v2-full/Dockerfile b/tests/fixtures/v2-full/Dockerfile
index 51ed0d90..6fa7a726 100644
--- a/tests/fixtures/v2-full/Dockerfile
+++ b/tests/fixtures/v2-full/Dockerfile
@@ -1,4 +1,4 @@
-FROM busybox:latest
+FROM busybox:1.31.0-uclibc
RUN echo something
CMD top
diff --git a/tests/fixtures/v2-full/docker-compose.yml b/tests/fixtures/v2-full/docker-compose.yml
index a973dd0c..20c14f0f 100644
--- a/tests/fixtures/v2-full/docker-compose.yml
+++ b/tests/fixtures/v2-full/docker-compose.yml
@@ -18,7 +18,7 @@ services:
- other
other:
- image: busybox:latest
+ image: busybox:1.31.0-uclibc
command: top
volumes:
- /data
diff --git a/tests/fixtures/v2-simple/links-invalid.yml b/tests/fixtures/v2-simple/links-invalid.yml
index 481aa404..a88eb1d5 100644
--- a/tests/fixtures/v2-simple/links-invalid.yml
+++ b/tests/fixtures/v2-simple/links-invalid.yml
@@ -1,10 +1,10 @@
version: "2"
services:
simple:
- image: busybox:latest
+ image: busybox:1.31.0-uclibc
command: top
links:
- another
another:
- image: busybox:latest
+ image: busybox:1.31.0-uclibc
command: top
diff --git a/tests/fixtures/v2-simple/one-container.yml b/tests/fixtures/v2-simple/one-container.yml
new file mode 100644
index 00000000..2d5c2ca6
--- /dev/null
+++ b/tests/fixtures/v2-simple/one-container.yml
@@ -0,0 +1,5 @@
+version: "2"
+services:
+ simple:
+ image: busybox:1.31.0-uclibc
+ command: top
diff --git a/tests/helpers.py b/tests/helpers.py
index dd129981..327715ee 100644
--- a/tests/helpers.py
+++ b/tests/helpers.py
@@ -7,6 +7,10 @@ from compose.config.config import ConfigDetails
from compose.config.config import ConfigFile
from compose.config.config import load
+BUSYBOX_IMAGE_NAME = 'busybox'
+BUSYBOX_DEFAULT_TAG = '1.31.0-uclibc'
+BUSYBOX_IMAGE_WITH_TAG = '{}:{}'.format(BUSYBOX_IMAGE_NAME, BUSYBOX_DEFAULT_TAG)
+
def build_config(contents, **kwargs):
return load(build_config_details(contents, **kwargs))
@@ -22,7 +26,7 @@ def build_config_details(contents, working_dir='working_dir', filename='filename
def create_custom_host_file(client, filename, content):
dirname = os.path.dirname(filename)
container = client.create_container(
- 'busybox:latest',
+ BUSYBOX_IMAGE_WITH_TAG,
['sh', '-c', 'echo -n "{}" > {}'.format(content, filename)],
volumes={dirname: {}},
host_config=client.create_host_config(
diff --git a/tests/integration/environment_test.py b/tests/integration/environment_test.py
new file mode 100644
index 00000000..671e6531
--- /dev/null
+++ b/tests/integration/environment_test.py
@@ -0,0 +1,70 @@
+from __future__ import absolute_import
+from __future__ import unicode_literals
+
+import tempfile
+
+from ddt import data
+from ddt import ddt
+
+from .. import mock
+from ..acceptance.cli_test import dispatch
+from compose.cli.command import get_project
+from compose.cli.command import project_from_options
+from compose.config.environment import Environment
+from tests.integration.testcases import DockerClientTestCase
+
+
+@ddt
+class EnvironmentTest(DockerClientTestCase):
+ @classmethod
+ def setUpClass(cls):
+ super(EnvironmentTest, cls).setUpClass()
+ cls.compose_file = tempfile.NamedTemporaryFile(mode='w+b')
+ cls.compose_file.write(bytes("""version: '3.2'
+services:
+ svc:
+ image: busybox:1.31.0-uclibc
+ environment:
+ TEST_VARIABLE: ${TEST_VARIABLE}""", encoding='utf-8'))
+ cls.compose_file.flush()
+
+ @classmethod
+ def tearDownClass(cls):
+ super(EnvironmentTest, cls).tearDownClass()
+ cls.compose_file.close()
+
+ @data('events',
+ 'exec',
+ 'kill',
+ 'logs',
+ 'pause',
+ 'ps',
+ 'restart',
+ 'rm',
+ 'start',
+ 'stop',
+ 'top',
+ 'unpause')
+ def _test_no_warning_on_missing_host_environment_var_on_silent_commands(self, cmd):
+ options = {'COMMAND': cmd, '--file': [EnvironmentTest.compose_file.name]}
+ with mock.patch('compose.config.environment.log') as fake_log:
+ # Note that the warning silencing and the env variables check is
+ # done in `project_from_options`
+ # So no need to have a proper options map, the `COMMAND` key is enough
+ project_from_options('.', options)
+ assert fake_log.warn.call_count == 0
+
+
+class EnvironmentOverrideFileTest(DockerClientTestCase):
+ def test_env_file_override(self):
+ base_dir = 'tests/fixtures/env-file-override'
+ dispatch(base_dir, ['--env-file', '.env.override', 'up'])
+ project = get_project(project_dir=base_dir,
+ config_path=['docker-compose.yml'],
+ environment=Environment.from_env_file(base_dir, '.env.override'),
+ override_dir=base_dir)
+ containers = project.containers(stopped=True)
+ assert len(containers) == 1
+ assert "WHEREAMI=override" in containers[0].get('Config.Env')
+ assert "DEFAULT_CONF_LOADED=true" in containers[0].get('Config.Env')
+ dispatch(base_dir, ['--env-file', '.env.override', 'down'], None)
diff --git a/tests/integration/project_test.py b/tests/integration/project_test.py
index 57f3b707..4c88f3d6 100644
--- a/tests/integration/project_test.py
+++ b/tests/integration/project_test.py
@@ -1,6 +1,7 @@
from __future__ import absolute_import
from __future__ import unicode_literals
+import copy
import json
import os
import random
@@ -14,6 +15,7 @@ from docker.errors import NotFound
from .. import mock
from ..helpers import build_config as load_config
+from ..helpers import BUSYBOX_IMAGE_WITH_TAG
from ..helpers import create_host_file
from .testcases import DockerClientTestCase
from .testcases import SWARM_SKIP_CONTAINERS_ALL
@@ -103,7 +105,7 @@ class ProjectTest(DockerClientTestCase):
self.create_service('extra').create_container()
project = Project('composetest', [web, db], self.client)
- assert set(project.containers(stopped=True)) == set([web_1, db_1])
+ assert set(project.containers(stopped=True)) == {web_1, db_1}
def test_parallel_pull_with_no_image(self):
config_data = build_config(
@@ -127,11 +129,11 @@ class ProjectTest(DockerClientTestCase):
name='composetest',
config_data=load_config({
'data': {
- 'image': 'busybox:latest',
+ 'image': BUSYBOX_IMAGE_WITH_TAG,
'volumes': ['/var/data'],
},
'db': {
- 'image': 'busybox:latest',
+ 'image': BUSYBOX_IMAGE_WITH_TAG,
'volumes_from': ['data'],
},
}),
@@ -144,7 +146,7 @@ class ProjectTest(DockerClientTestCase):
def test_volumes_from_container(self):
data_container = Container.create(
self.client,
- image='busybox:latest',
+ image=BUSYBOX_IMAGE_WITH_TAG,
volumes=['/var/data'],
name='composetest_data_container',
labels={LABEL_PROJECT: 'composetest'},
@@ -154,7 +156,7 @@ class ProjectTest(DockerClientTestCase):
name='composetest',
config_data=load_config({
'db': {
- 'image': 'busybox:latest',
+ 'image': BUSYBOX_IMAGE_WITH_TAG,
'volumes_from': ['composetest_data_container'],
},
}),
@@ -173,11 +175,11 @@ class ProjectTest(DockerClientTestCase):
'version': str(V2_0),
'services': {
'net': {
- 'image': 'busybox:latest',
+ 'image': BUSYBOX_IMAGE_WITH_TAG,
'command': ["top"]
},
'web': {
- 'image': 'busybox:latest',
+ 'image': BUSYBOX_IMAGE_WITH_TAG,
'network_mode': 'service:net',
'command': ["top"]
},
@@ -201,7 +203,7 @@ class ProjectTest(DockerClientTestCase):
'version': str(V2_0),
'services': {
'web': {
- 'image': 'busybox:latest',
+ 'image': BUSYBOX_IMAGE_WITH_TAG,
'network_mode': 'container:composetest_net_container'
},
},
@@ -216,7 +218,7 @@ class ProjectTest(DockerClientTestCase):
net_container = Container.create(
self.client,
- image='busybox:latest',
+ image=BUSYBOX_IMAGE_WITH_TAG,
name='composetest_net_container',
command='top',
labels={LABEL_PROJECT: 'composetest'},
@@ -236,11 +238,11 @@ class ProjectTest(DockerClientTestCase):
name='composetest',
config_data=load_config({
'net': {
- 'image': 'busybox:latest',
+ 'image': BUSYBOX_IMAGE_WITH_TAG,
'command': ["top"]
},
'web': {
- 'image': 'busybox:latest',
+ 'image': BUSYBOX_IMAGE_WITH_TAG,
'net': 'container:net',
'command': ["top"]
},
@@ -261,7 +263,7 @@ class ProjectTest(DockerClientTestCase):
name='composetest',
config_data=load_config({
'web': {
- 'image': 'busybox:latest',
+ 'image': BUSYBOX_IMAGE_WITH_TAG,
'net': 'container:composetest_net_container'
},
}),
@@ -275,7 +277,7 @@ class ProjectTest(DockerClientTestCase):
net_container = Container.create(
self.client,
- image='busybox:latest',
+ image=BUSYBOX_IMAGE_WITH_TAG,
name='composetest_net_container',
command='top',
labels={LABEL_PROJECT: 'composetest'},
@@ -304,24 +306,20 @@ class ProjectTest(DockerClientTestCase):
db_container = db.create_container()
project.start(service_names=['web'])
- assert set(c.name for c in project.containers() if c.is_running) == set(
- [web_container_1.name, web_container_2.name]
- )
+ assert set(c.name for c in project.containers() if c.is_running) == {
+ web_container_1.name, web_container_2.name}
project.start()
- assert set(c.name for c in project.containers() if c.is_running) == set(
- [web_container_1.name, web_container_2.name, db_container.name]
- )
+ assert set(c.name for c in project.containers() if c.is_running) == {
+ web_container_1.name, web_container_2.name, db_container.name}
project.pause(service_names=['web'])
- assert set([c.name for c in project.containers() if c.is_paused]) == set(
- [web_container_1.name, web_container_2.name]
- )
+ assert set([c.name for c in project.containers() if c.is_paused]) == {
+ web_container_1.name, web_container_2.name}
project.pause()
- assert set([c.name for c in project.containers() if c.is_paused]) == set(
- [web_container_1.name, web_container_2.name, db_container.name]
- )
+ assert set([c.name for c in project.containers() if c.is_paused]) == {
+ web_container_1.name, web_container_2.name, db_container.name}
project.unpause(service_names=['db'])
assert len([c.name for c in project.containers() if c.is_paused]) == 2
@@ -330,7 +328,7 @@ class ProjectTest(DockerClientTestCase):
assert len([c.name for c in project.containers() if c.is_paused]) == 0
project.stop(service_names=['web'], timeout=1)
- assert set(c.name for c in project.containers() if c.is_running) == set([db_container.name])
+ assert set(c.name for c in project.containers() if c.is_running) == {db_container.name}
project.kill(service_names=['db'])
assert len([c for c in project.containers() if c.is_running]) == 0
@@ -552,20 +550,20 @@ class ProjectTest(DockerClientTestCase):
name='composetest',
config_data=load_config({
'console': {
- 'image': 'busybox:latest',
+ 'image': BUSYBOX_IMAGE_WITH_TAG,
'command': ["top"],
},
'data': {
- 'image': 'busybox:latest',
+ 'image': BUSYBOX_IMAGE_WITH_TAG,
'command': ["top"]
},
'db': {
- 'image': 'busybox:latest',
+ 'image': BUSYBOX_IMAGE_WITH_TAG,
'command': ["top"],
'volumes_from': ['data'],
},
'web': {
- 'image': 'busybox:latest',
+ 'image': BUSYBOX_IMAGE_WITH_TAG,
'command': ["top"],
'links': ['db'],
},
@@ -587,20 +585,20 @@ class ProjectTest(DockerClientTestCase):
name='composetest',
config_data=load_config({
'console': {
- 'image': 'busybox:latest',
+ 'image': BUSYBOX_IMAGE_WITH_TAG,
'command': ["top"],
},
'data': {
- 'image': 'busybox:latest',
+ 'image': BUSYBOX_IMAGE_WITH_TAG,
'command': ["top"]
},
'db': {
- 'image': 'busybox:latest',
+ 'image': BUSYBOX_IMAGE_WITH_TAG,
'command': ["top"],
'volumes_from': ['data'],
},
'web': {
- 'image': 'busybox:latest',
+ 'image': BUSYBOX_IMAGE_WITH_TAG,
'command': ["top"],
'links': ['db'],
},
@@ -626,7 +624,7 @@ class ProjectTest(DockerClientTestCase):
'version': '2.1',
'services': {
'foo': {
- 'image': 'busybox:latest',
+ 'image': BUSYBOX_IMAGE_WITH_TAG,
'tmpfs': ['/dev/shm'],
'volumes': ['/dev/shm']
}
@@ -667,7 +665,7 @@ class ProjectTest(DockerClientTestCase):
version=V2_0,
services=[{
'name': 'web',
- 'image': 'busybox:latest',
+ 'image': BUSYBOX_IMAGE_WITH_TAG,
'command': 'top',
'networks': {
'foo': None,
@@ -712,7 +710,7 @@ class ProjectTest(DockerClientTestCase):
version=V2_0,
services=[{
'name': 'web',
- 'image': 'busybox:latest',
+ 'image': BUSYBOX_IMAGE_WITH_TAG,
'networks': {'front': None},
}],
networks={
@@ -772,7 +770,7 @@ class ProjectTest(DockerClientTestCase):
version=V2_0,
services=[{
'name': 'web',
- 'image': 'busybox:latest',
+ 'image': BUSYBOX_IMAGE_WITH_TAG,
'networks': {'front': None},
}],
networks={
@@ -807,7 +805,7 @@ class ProjectTest(DockerClientTestCase):
version=V2_1,
services=[{
'name': 'web',
- 'image': 'busybox:latest',
+ 'image': BUSYBOX_IMAGE_WITH_TAG,
'command': 'top',
'networks': {
'static_test': {
@@ -859,7 +857,7 @@ class ProjectTest(DockerClientTestCase):
version=V2_3,
services=[{
'name': 'web',
- 'image': 'busybox:latest',
+ 'image': BUSYBOX_IMAGE_WITH_TAG,
'networks': {
'n1': {
'priority': p1,
@@ -922,7 +920,7 @@ class ProjectTest(DockerClientTestCase):
version=V2_1,
services=[{
'name': 'web',
- 'image': 'busybox:latest',
+ 'image': BUSYBOX_IMAGE_WITH_TAG,
'command': 'top',
'networks': {
'static_test': {
@@ -965,7 +963,7 @@ class ProjectTest(DockerClientTestCase):
version=V2_0,
services=[{
'name': 'web',
- 'image': 'busybox:latest',
+ 'image': BUSYBOX_IMAGE_WITH_TAG,
'networks': {
'static_test': {
'ipv4_address': '172.16.100.100',
@@ -1001,7 +999,7 @@ class ProjectTest(DockerClientTestCase):
version=V2_1,
services=[{
'name': 'web',
- 'image': 'busybox:latest',
+ 'image': BUSYBOX_IMAGE_WITH_TAG,
'networks': {
'linklocaltest': {
'link_local_ips': ['169.254.8.8']
@@ -1038,7 +1036,7 @@ class ProjectTest(DockerClientTestCase):
'name': 'web',
'volumes': [VolumeSpec.parse('foo:/container-path')],
'networks': {'foo': {}},
- 'image': 'busybox:latest'
+ 'image': BUSYBOX_IMAGE_WITH_TAG
}],
networks={
'foo': {
@@ -1074,7 +1072,7 @@ class ProjectTest(DockerClientTestCase):
version=V2_1,
services=[{
'name': 'web',
- 'image': 'busybox:latest',
+ 'image': BUSYBOX_IMAGE_WITH_TAG,
'isolation': 'default'
}],
)
@@ -1094,7 +1092,7 @@ class ProjectTest(DockerClientTestCase):
version=V2_1,
services=[{
'name': 'web',
- 'image': 'busybox:latest',
+ 'image': BUSYBOX_IMAGE_WITH_TAG,
'isolation': 'foobar'
}],
)
@@ -1114,7 +1112,7 @@ class ProjectTest(DockerClientTestCase):
version=V2_3,
services=[{
'name': 'web',
- 'image': 'busybox:latest',
+ 'image': BUSYBOX_IMAGE_WITH_TAG,
'runtime': 'runc'
}],
)
@@ -1134,7 +1132,7 @@ class ProjectTest(DockerClientTestCase):
version=V2_3,
services=[{
'name': 'web',
- 'image': 'busybox:latest',
+ 'image': BUSYBOX_IMAGE_WITH_TAG,
'runtime': 'foobar'
}],
)
@@ -1154,7 +1152,7 @@ class ProjectTest(DockerClientTestCase):
version=V2_3,
services=[{
'name': 'web',
- 'image': 'busybox:latest',
+ 'image': BUSYBOX_IMAGE_WITH_TAG,
'runtime': 'nvidia'
}],
)
@@ -1174,7 +1172,7 @@ class ProjectTest(DockerClientTestCase):
version=V2_0,
services=[{
'name': 'web',
- 'image': 'busybox:latest',
+ 'image': BUSYBOX_IMAGE_WITH_TAG,
'networks': {'internal': None},
}],
networks={
@@ -1203,7 +1201,7 @@ class ProjectTest(DockerClientTestCase):
version=V2_1,
services=[{
'name': 'web',
- 'image': 'busybox:latest',
+ 'image': BUSYBOX_IMAGE_WITH_TAG,
'networks': {network_name: None}
}],
networks={
@@ -1236,7 +1234,7 @@ class ProjectTest(DockerClientTestCase):
version=V2_0,
services=[{
'name': 'web',
- 'image': 'busybox:latest',
+ 'image': BUSYBOX_IMAGE_WITH_TAG,
'command': 'top'
}],
volumes={vol_name: {'driver': 'local'}},
@@ -1263,7 +1261,7 @@ class ProjectTest(DockerClientTestCase):
version=V2_1,
services=[{
'name': 'web',
- 'image': 'busybox:latest',
+ 'image': BUSYBOX_IMAGE_WITH_TAG,
'volumes': [VolumeSpec.parse('{}:/data'.format(volume_name))]
}],
volumes={
@@ -1302,9 +1300,9 @@ class ProjectTest(DockerClientTestCase):
{
'version': str(V2_0),
'services': {
- 'simple': {'image': 'busybox:latest', 'command': 'top'},
+ 'simple': {'image': BUSYBOX_IMAGE_WITH_TAG, 'command': 'top'},
'another': {
- 'image': 'busybox:latest',
+ 'image': BUSYBOX_IMAGE_WITH_TAG,
'command': 'top',
'logging': {
'driver': "json-file",
@@ -1355,7 +1353,7 @@ class ProjectTest(DockerClientTestCase):
'version': str(V2_0),
'services': {
'simple': {
- 'image': 'busybox:latest',
+ 'image': BUSYBOX_IMAGE_WITH_TAG,
'command': 'top',
'ports': ['1234:1234']
},
@@ -1389,7 +1387,7 @@ class ProjectTest(DockerClientTestCase):
version=V2_2,
services=[{
'name': 'web',
- 'image': 'busybox:latest',
+ 'image': BUSYBOX_IMAGE_WITH_TAG,
'command': 'top',
'scale': 3
}]
@@ -1419,7 +1417,7 @@ class ProjectTest(DockerClientTestCase):
version=V2_0,
services=[{
'name': 'web',
- 'image': 'busybox:latest',
+ 'image': BUSYBOX_IMAGE_WITH_TAG,
'command': 'top'
}],
volumes={vol_name: {}},
@@ -1443,7 +1441,7 @@ class ProjectTest(DockerClientTestCase):
version=V2_0,
services=[{
'name': 'web',
- 'image': 'busybox:latest',
+ 'image': BUSYBOX_IMAGE_WITH_TAG,
'command': 'top'
}],
volumes={vol_name: {}},
@@ -1467,7 +1465,7 @@ class ProjectTest(DockerClientTestCase):
version=V3_1,
services=[{
'name': 'web',
- 'image': 'busybox:latest',
+ 'image': BUSYBOX_IMAGE_WITH_TAG,
'command': 'cat /run/secrets/special',
'secrets': [
types.ServiceSecret.parse({'source': 'super', 'target': 'special'}),
@@ -1496,6 +1494,60 @@ class ProjectTest(DockerClientTestCase):
output = container.logs()
assert output == b"This is the secret\n"
+ @v3_only()
+ def test_project_up_with_added_secrets(self):
+ node = create_host_file(self.client, os.path.abspath('tests/fixtures/secrets/default'))
+
+ config_input1 = {
+ 'version': V3_1,
+ 'services': [
+ {
+ 'name': 'web',
+ 'image': BUSYBOX_IMAGE_WITH_TAG,
+ 'command': 'cat /run/secrets/special',
+ 'environment': ['constraint:node=={}'.format(node if node is not None else '')]
+ }
+
+ ],
+ 'secrets': {
+ 'super': {
+ 'file': os.path.abspath('tests/fixtures/secrets/default')
+ }
+ }
+ }
+ config_input2 = copy.deepcopy(config_input1)
+ # Add the secret
+ config_input2['services'][0]['secrets'] = [
+ types.ServiceSecret.parse({'source': 'super', 'target': 'special'})
+ ]
+
+ config_data1 = build_config(**config_input1)
+ config_data2 = build_config(**config_input2)
+
+ # First up with non-secret
+ project = Project.from_config(
+ client=self.client,
+ name='composetest',
+ config_data=config_data1,
+ )
+ project.up()
+
+ # Then up with secret
+ project = Project.from_config(
+ client=self.client,
+ name='composetest',
+ config_data=config_data2,
+ )
+ project.up()
+ project.stop()
+
+ containers = project.containers(stopped=True)
+ assert len(containers) == 1
+ container, = containers
+
+ output = container.logs()
+ assert output == b"This is the secret\n"
+
@v2_only()
def test_initialize_volumes_invalid_volume_driver(self):
vol_name = '{0:x}'.format(random.getrandbits(32))
@@ -1504,7 +1556,7 @@ class ProjectTest(DockerClientTestCase):
version=V2_0,
services=[{
'name': 'web',
- 'image': 'busybox:latest',
+ 'image': BUSYBOX_IMAGE_WITH_TAG,
'command': 'top'
}],
volumes={vol_name: {'driver': 'foobar'}},
@@ -1527,7 +1579,7 @@ class ProjectTest(DockerClientTestCase):
version=V2_0,
services=[{
'name': 'web',
- 'image': 'busybox:latest',
+ 'image': BUSYBOX_IMAGE_WITH_TAG,
'command': 'top'
}],
volumes={vol_name: {'driver': 'local'}},
@@ -1569,7 +1621,7 @@ class ProjectTest(DockerClientTestCase):
version=V2_0,
services=[{
'name': 'web',
- 'image': 'busybox:latest',
+ 'image': BUSYBOX_IMAGE_WITH_TAG,
'command': 'top'
}],
volumes={
@@ -1611,7 +1663,7 @@ class ProjectTest(DockerClientTestCase):
version=V2_0,
services=[{
'name': 'web',
- 'image': 'busybox:latest',
+ 'image': BUSYBOX_IMAGE_WITH_TAG,
'command': 'top'
}],
volumes={vol_name: {'driver': 'local'}},
@@ -1650,7 +1702,7 @@ class ProjectTest(DockerClientTestCase):
version=V2_0,
services=[{
'name': 'web',
- 'image': 'busybox:latest',
+ 'image': BUSYBOX_IMAGE_WITH_TAG,
'command': 'top'
}],
volumes={
@@ -1674,7 +1726,7 @@ class ProjectTest(DockerClientTestCase):
version=V2_0,
services=[{
'name': 'web',
- 'image': 'busybox:latest',
+ 'image': BUSYBOX_IMAGE_WITH_TAG,
'command': 'top'
}],
volumes={
@@ -1702,7 +1754,7 @@ class ProjectTest(DockerClientTestCase):
'version': str(V2_0),
'services': {
'simple': {
- 'image': 'busybox:latest',
+ 'image': BUSYBOX_IMAGE_WITH_TAG,
'command': 'top',
'volumes': ['{0}:/data'.format(vol_name)]
},
@@ -1731,7 +1783,7 @@ class ProjectTest(DockerClientTestCase):
def test_project_up_orphans(self):
config_dict = {
'service1': {
- 'image': 'busybox:latest',
+ 'image': BUSYBOX_IMAGE_WITH_TAG,
'command': 'top',
}
}
@@ -1768,7 +1820,7 @@ class ProjectTest(DockerClientTestCase):
def test_project_up_ignore_orphans(self):
config_dict = {
'service1': {
- 'image': 'busybox:latest',
+ 'image': BUSYBOX_IMAGE_WITH_TAG,
'command': 'top',
}
}
@@ -1796,7 +1848,7 @@ class ProjectTest(DockerClientTestCase):
'version': '2.1',
'services': {
'svc1': {
- 'image': 'busybox:latest',
+ 'image': BUSYBOX_IMAGE_WITH_TAG,
'command': 'top',
'healthcheck': {
'test': 'exit 0',
@@ -1806,7 +1858,7 @@ class ProjectTest(DockerClientTestCase):
},
},
'svc2': {
- 'image': 'busybox:latest',
+ 'image': BUSYBOX_IMAGE_WITH_TAG,
'command': 'top',
'depends_on': {
'svc1': {'condition': 'service_healthy'},
@@ -1833,7 +1885,7 @@ class ProjectTest(DockerClientTestCase):
'version': '2.1',
'services': {
'svc1': {
- 'image': 'busybox:latest',
+ 'image': BUSYBOX_IMAGE_WITH_TAG,
'command': 'top',
'healthcheck': {
'test': 'exit 1',
@@ -1843,7 +1895,7 @@ class ProjectTest(DockerClientTestCase):
},
},
'svc2': {
- 'image': 'busybox:latest',
+ 'image': BUSYBOX_IMAGE_WITH_TAG,
'command': 'top',
'depends_on': {
'svc1': {'condition': 'service_healthy'},
@@ -1872,14 +1924,14 @@ class ProjectTest(DockerClientTestCase):
'version': '2.1',
'services': {
'svc1': {
- 'image': 'busybox:latest',
+ 'image': BUSYBOX_IMAGE_WITH_TAG,
'command': 'top',
'healthcheck': {
'disable': True
},
},
'svc2': {
- 'image': 'busybox:latest',
+ 'image': BUSYBOX_IMAGE_WITH_TAG,
'command': 'top',
'depends_on': {
'svc1': {'condition': 'service_healthy'},
@@ -1916,7 +1968,7 @@ class ProjectTest(DockerClientTestCase):
'version': '2.3',
'services': {
'svc1': {
- 'image': 'busybox:latest',
+ 'image': BUSYBOX_IMAGE_WITH_TAG,
'command': 'top',
'security_opt': ['seccomp:"{}"'.format(profile_path)]
}
@@ -1940,7 +1992,7 @@ class ProjectTest(DockerClientTestCase):
'version': '2.3',
'services': {
'svc1': {
- 'image': 'busybox:latest',
+ 'image': BUSYBOX_IMAGE_WITH_TAG,
'command': 'ls',
'volumes': ['foo:/foo:rw'],
'networks': ['bar'],
diff --git a/tests/integration/service_test.py b/tests/integration/service_test.py
index 000f6838..9750f581 100644
--- a/tests/integration/service_test.py
+++ b/tests/integration/service_test.py
@@ -15,6 +15,7 @@ from six import StringIO
from six import text_type
from .. import mock
+from ..helpers import BUSYBOX_IMAGE_WITH_TAG
from .testcases import docker_client
from .testcases import DockerClientTestCase
from .testcases import get_links
@@ -373,7 +374,7 @@ class ServiceTest(DockerClientTestCase):
self.client.create_volume(volume_name)
service = Service('db', client=client, volumes=[
MountSpec(type='volume', source=volume_name, target=container_path)
- ], image='busybox:latest', command=['top'], project='composetest')
+ ], image=BUSYBOX_IMAGE_WITH_TAG, command=['top'], project='composetest')
container = service.create_container()
service.start_container(container)
mount = container.get_mount(container_path)
@@ -388,7 +389,7 @@ class ServiceTest(DockerClientTestCase):
container_path = '/container-tmpfs'
service = Service('db', client=client, volumes=[
MountSpec(type='tmpfs', target=container_path)
- ], image='busybox:latest', command=['top'], project='composetest')
+ ], image=BUSYBOX_IMAGE_WITH_TAG, command=['top'], project='composetest')
container = service.create_container()
service.start_container(container)
mount = container.get_mount(container_path)
@@ -474,7 +475,7 @@ class ServiceTest(DockerClientTestCase):
volume_container_1 = volume_service.create_container()
volume_container_2 = Container.create(
self.client,
- image='busybox:latest',
+ image=BUSYBOX_IMAGE_WITH_TAG,
command=["top"],
labels={LABEL_PROJECT: 'composetest'},
host_config={},
@@ -695,8 +696,8 @@ class ServiceTest(DockerClientTestCase):
new_container, = service.execute_convergence_plan(
ConvergencePlan('recreate', [old_container]))
- mock_log.warn.assert_called_once_with(mock.ANY)
- _, args, kwargs = mock_log.warn.mock_calls[0]
+ mock_log.warning.assert_called_once_with(mock.ANY)
+ _, args, kwargs = mock_log.warning.mock_calls[0]
assert "Service \"db\" is using volume \"/data\" from the previous container" in args[0]
assert [mount['Destination'] for mount in new_container.get('Mounts')] == ['/data']
@@ -1232,9 +1233,8 @@ class ServiceTest(DockerClientTestCase):
# })
def test_create_with_image_id(self):
- # Get image id for the current busybox:latest
pull_busybox(self.client)
- image_id = self.client.inspect_image('busybox:latest')['Id'][:12]
+ image_id = self.client.inspect_image(BUSYBOX_IMAGE_WITH_TAG)['Id'][:12]
service = self.create_service('foo', image=image_id)
service.create_container()
@@ -1382,7 +1382,7 @@ class ServiceTest(DockerClientTestCase):
with pytest.raises(OperationFailedError):
service.scale(3)
- captured_output = mock_log.warn.call_args[0][0]
+ captured_output = mock_log.warning.call_args[0][0]
assert len(service.containers()) == 1
assert "Remove the custom name to scale the service." in captured_output
diff --git a/tests/integration/state_test.py b/tests/integration/state_test.py
index b7d38a4b..714945ee 100644
--- a/tests/integration/state_test.py
+++ b/tests/integration/state_test.py
@@ -5,9 +5,12 @@ by `docker-compose up`.
from __future__ import absolute_import
from __future__ import unicode_literals
+import copy
+
import py
from docker.errors import ImageNotFound
+from ..helpers import BUSYBOX_IMAGE_WITH_TAG
from .testcases import DockerClientTestCase
from .testcases import get_links
from .testcases import no_cluster
@@ -40,8 +43,8 @@ class BasicProjectTest(ProjectTestCase):
super(BasicProjectTest, self).setUp()
self.cfg = {
- 'db': {'image': 'busybox:latest', 'command': 'top'},
- 'web': {'image': 'busybox:latest', 'command': 'top'},
+ 'db': {'image': BUSYBOX_IMAGE_WITH_TAG, 'command': 'top'},
+ 'web': {'image': BUSYBOX_IMAGE_WITH_TAG, 'command': 'top'},
}
def test_no_change(self):
@@ -97,16 +100,16 @@ class ProjectWithDependenciesTest(ProjectTestCase):
self.cfg = {
'db': {
- 'image': 'busybox:latest',
+ 'image': BUSYBOX_IMAGE_WITH_TAG,
'command': 'tail -f /dev/null',
},
'web': {
- 'image': 'busybox:latest',
+ 'image': BUSYBOX_IMAGE_WITH_TAG,
'command': 'tail -f /dev/null',
'links': ['db'],
},
'nginx': {
- 'image': 'busybox:latest',
+ 'image': BUSYBOX_IMAGE_WITH_TAG,
'command': 'tail -f /dev/null',
'links': ['web'],
},
@@ -171,7 +174,7 @@ class ProjectWithDependenciesTest(ProjectTestCase):
def test_service_removed_while_down(self):
next_cfg = {
'web': {
- 'image': 'busybox:latest',
+ 'image': BUSYBOX_IMAGE_WITH_TAG,
'command': 'tail -f /dev/null',
},
'nginx': self.cfg['nginx'],
@@ -209,6 +212,143 @@ class ProjectWithDependenciesTest(ProjectTestCase):
}
+class ProjectWithDependsOnDependenciesTest(ProjectTestCase):
+ def setUp(self):
+ super(ProjectWithDependsOnDependenciesTest, self).setUp()
+
+ self.cfg = {
+ 'version': '2',
+ 'services': {
+ 'db': {
+ 'image': BUSYBOX_IMAGE_WITH_TAG,
+ 'command': 'tail -f /dev/null',
+ },
+ 'web': {
+ 'image': BUSYBOX_IMAGE_WITH_TAG,
+ 'command': 'tail -f /dev/null',
+ 'depends_on': ['db'],
+ },
+ 'nginx': {
+ 'image': BUSYBOX_IMAGE_WITH_TAG,
+ 'command': 'tail -f /dev/null',
+ 'depends_on': ['web'],
+ },
+ }
+ }
+
+ def test_up(self):
+ local_cfg = copy.deepcopy(self.cfg)
+ containers = self.run_up(local_cfg)
+ assert set(c.service for c in containers) == set(['db', 'web', 'nginx'])
+
+ def test_change_leaf(self):
+ local_cfg = copy.deepcopy(self.cfg)
+ old_containers = self.run_up(local_cfg)
+
+ local_cfg['services']['nginx']['environment'] = {'NEW_VAR': '1'}
+ new_containers = self.run_up(local_cfg)
+
+ assert set(c.service for c in new_containers - old_containers) == set(['nginx'])
+
+ def test_change_middle(self):
+ local_cfg = copy.deepcopy(self.cfg)
+ old_containers = self.run_up(local_cfg)
+
+ local_cfg['services']['web']['environment'] = {'NEW_VAR': '1'}
+ new_containers = self.run_up(local_cfg)
+
+ assert set(c.service for c in new_containers - old_containers) == set(['web'])
+
+ def test_change_middle_always_recreate_deps(self):
+ local_cfg = copy.deepcopy(self.cfg)
+ old_containers = self.run_up(local_cfg, always_recreate_deps=True)
+
+ local_cfg['services']['web']['environment'] = {'NEW_VAR': '1'}
+ new_containers = self.run_up(local_cfg, always_recreate_deps=True)
+
+ assert set(c.service for c in new_containers - old_containers) == set(['web', 'nginx'])
+
+ def test_change_root(self):
+ local_cfg = copy.deepcopy(self.cfg)
+ old_containers = self.run_up(local_cfg)
+
+ local_cfg['services']['db']['environment'] = {'NEW_VAR': '1'}
+ new_containers = self.run_up(local_cfg)
+
+ assert set(c.service for c in new_containers - old_containers) == set(['db'])
+
+ def test_change_root_always_recreate_deps(self):
+ local_cfg = copy.deepcopy(self.cfg)
+ old_containers = self.run_up(local_cfg, always_recreate_deps=True)
+
+ local_cfg['services']['db']['environment'] = {'NEW_VAR': '1'}
+ new_containers = self.run_up(local_cfg, always_recreate_deps=True)
+
+ assert set(c.service for c in new_containers - old_containers) == set(['db', 'web', 'nginx'])
+
+ def test_change_root_no_recreate(self):
+ local_cfg = copy.deepcopy(self.cfg)
+ old_containers = self.run_up(local_cfg)
+
+ local_cfg['services']['db']['environment'] = {'NEW_VAR': '1'}
+ new_containers = self.run_up(
+ local_cfg,
+ strategy=ConvergenceStrategy.never)
+
+ assert new_containers - old_containers == set()
+
+ def test_service_removed_while_down(self):
+ local_cfg = copy.deepcopy(self.cfg)
+ next_cfg = copy.deepcopy(self.cfg)
+ del next_cfg['services']['db']
+ del next_cfg['services']['web']['depends_on']
+
+ containers = self.run_up(local_cfg)
+ assert set(c.service for c in containers) == set(['db', 'web', 'nginx'])
+
+ project = self.make_project(local_cfg)
+ project.stop(timeout=1)
+
+ next_containers = self.run_up(next_cfg)
+ assert set(c.service for c in next_containers) == set(['web', 'nginx'])
+
+ def test_service_removed_while_up(self):
+ local_cfg = copy.deepcopy(self.cfg)
+ containers = self.run_up(local_cfg)
+ assert set(c.service for c in containers) == set(['db', 'web', 'nginx'])
+
+ del local_cfg['services']['db']
+ del local_cfg['services']['web']['depends_on']
+
+ containers = self.run_up(local_cfg)
+ assert set(c.service for c in containers) == set(['web', 'nginx'])
+
+ def test_dependency_removed(self):
+ local_cfg = copy.deepcopy(self.cfg)
+ next_cfg = copy.deepcopy(self.cfg)
+ del next_cfg['services']['nginx']['depends_on']
+
+ containers = self.run_up(local_cfg, service_names=['nginx'])
+ assert set(c.service for c in containers) == set(['db', 'web', 'nginx'])
+
+ project = self.make_project(local_cfg)
+ project.stop(timeout=1)
+
+ next_containers = self.run_up(next_cfg, service_names=['nginx'])
+ assert set(c.service for c in next_containers if c.is_running) == set(['nginx'])
+
+ def test_dependency_added(self):
+ local_cfg = copy.deepcopy(self.cfg)
+
+ del local_cfg['services']['nginx']['depends_on']
+ containers = self.run_up(local_cfg, service_names=['nginx'])
+ assert set(c.service for c in containers) == set(['nginx'])
+
+ local_cfg['services']['nginx']['depends_on'] = ['db']
+ containers = self.run_up(local_cfg, service_names=['nginx'])
+ assert set(c.service for c in containers) == set(['nginx', 'db'])
+
+
class ServiceStateTest(DockerClientTestCase):
"""Test cases for Service.convergence_plan."""
@@ -246,7 +386,7 @@ class ServiceStateTest(DockerClientTestCase):
assert ('recreate', [container]) == web.convergence_plan()
def test_trigger_recreate_with_nonexistent_image_tag(self):
- web = self.create_service('web', image="busybox:latest")
+ web = self.create_service('web', image=BUSYBOX_IMAGE_WITH_TAG)
container = web.create_container()
web = self.create_service('web', image="nonexistent-image")
diff --git a/tests/integration/testcases.py b/tests/integration/testcases.py
index cfdf22f7..fe70d1f7 100644
--- a/tests/integration/testcases.py
+++ b/tests/integration/testcases.py
@@ -9,6 +9,7 @@ from docker.errors import APIError
from docker.utils import version_lt
from .. import unittest
+from ..helpers import BUSYBOX_IMAGE_WITH_TAG
from compose.cli.docker_client import docker_client
from compose.config.config import resolve_environment
from compose.config.environment import Environment
@@ -32,7 +33,7 @@ SWARM_ASSUME_MULTINODE = os.environ.get('SWARM_ASSUME_MULTINODE', '0') != '0'
def pull_busybox(client):
- client.pull('busybox:latest', stream=False)
+ client.pull(BUSYBOX_IMAGE_WITH_TAG, stream=False)
def get_links(container):
@@ -123,7 +124,7 @@ class DockerClientTestCase(unittest.TestCase):
def create_service(self, name, **kwargs):
if 'image' not in kwargs and 'build' not in kwargs:
- kwargs['image'] = 'busybox:latest'
+ kwargs['image'] = BUSYBOX_IMAGE_WITH_TAG
if 'command' not in kwargs:
kwargs['command'] = ["top"]
diff --git a/tests/unit/bundle_test.py b/tests/unit/bundle_test.py
index 88f75405..8faebb7f 100644
--- a/tests/unit/bundle_test.py
+++ b/tests/unit/bundle_test.py
@@ -10,6 +10,7 @@ from compose import service
from compose.cli.errors import UserError
from compose.config.config import Config
from compose.const import COMPOSEFILE_V2_0 as V2_0
+from compose.service import NoSuchImageError
@pytest.fixture
@@ -35,6 +36,16 @@ def test_get_image_digest_image_uses_digest(mock_service):
assert not mock_service.image.called
+def test_get_image_digest_from_repository(mock_service):
+ mock_service.options['image'] = 'abcd'
+ mock_service.image_name = 'abcd'
+ mock_service.image.side_effect = NoSuchImageError(None)
+ mock_service.get_image_registry_data.return_value = {'Descriptor': {'digest': 'digest'}}
+
+ digest = bundle.get_image_digest(mock_service)
+ assert digest == 'abcd@digest'
+
+
def test_get_image_digest_no_image(mock_service):
with pytest.raises(UserError) as exc:
bundle.get_image_digest(service.Service(name='theservice'))
@@ -83,7 +94,7 @@ def test_to_bundle():
configs={}
)
- with mock.patch('compose.bundle.log.warn', autospec=True) as mock_log:
+ with mock.patch('compose.bundle.log.warning', autospec=True) as mock_log:
output = bundle.to_bundle(config, image_digests)
assert mock_log.mock_calls == [
@@ -117,7 +128,7 @@ def test_convert_service_to_bundle():
'privileged': True,
}
- with mock.patch('compose.bundle.log.warn', autospec=True) as mock_log:
+ with mock.patch('compose.bundle.log.warning', autospec=True) as mock_log:
config = bundle.convert_service_to_bundle(name, service_dict, image_digest)
mock_log.assert_called_once_with(
@@ -166,7 +177,7 @@ def test_make_service_networks_default():
name = 'theservice'
service_dict = {}
- with mock.patch('compose.bundle.log.warn', autospec=True) as mock_log:
+ with mock.patch('compose.bundle.log.warning', autospec=True) as mock_log:
networks = bundle.make_service_networks(name, service_dict)
assert not mock_log.called
@@ -184,7 +195,7 @@ def test_make_service_networks():
},
}
- with mock.patch('compose.bundle.log.warn', autospec=True) as mock_log:
+ with mock.patch('compose.bundle.log.warning', autospec=True) as mock_log:
networks = bundle.make_service_networks(name, service_dict)
mock_log.assert_called_once_with(
diff --git a/tests/unit/cli/docker_client_test.py b/tests/unit/cli/docker_client_test.py
index be91ea31..772c136e 100644
--- a/tests/unit/cli/docker_client_test.py
+++ b/tests/unit/cli/docker_client_test.py
@@ -247,5 +247,5 @@ class TestGetTlsVersion(object):
environment = {'COMPOSE_TLS_VERSION': 'TLSv5_5'}
with mock.patch('compose.cli.docker_client.log') as mock_log:
tls_version = get_tls_version(environment)
- mock_log.warn.assert_called_once_with(mock.ANY)
+ mock_log.warning.assert_called_once_with(mock.ANY)
assert tls_version is None
diff --git a/tests/unit/cli/main_test.py b/tests/unit/cli/main_test.py
index 2e97f2c8..aadb9d45 100644
--- a/tests/unit/cli/main_test.py
+++ b/tests/unit/cli/main_test.py
@@ -9,9 +9,11 @@ import pytest
from compose import container
from compose.cli.errors import UserError
from compose.cli.formatter import ConsoleWarningFormatter
+from compose.cli.main import build_one_off_container_options
from compose.cli.main import call_docker
from compose.cli.main import convergence_strategy_from_opts
from compose.cli.main import filter_containers_to_service_names
+from compose.cli.main import get_docker_start_call
from compose.cli.main import setup_console_handler
from compose.cli.main import warn_for_swarm_mode
from compose.service import ConvergenceStrategy
@@ -63,7 +65,65 @@ class TestCLIMainTestCase(object):
with mock.patch('compose.cli.main.log') as fake_log:
warn_for_swarm_mode(mock_client)
- assert fake_log.warn.call_count == 1
+ assert fake_log.warning.call_count == 1
+
+ def test_build_one_off_container_options(self):
+ command = 'build myservice'
+ detach = False
+ options = {
+ '-e': ['MYVAR=MYVALUE'],
+ '-T': True,
+ '--label': ['MYLABEL'],
+ '--entrypoint': 'bash',
+ '--user': 'MYUSER',
+ '--service-ports': [],
+ '--publish': '',
+ '--name': 'MYNAME',
+ '--workdir': '.',
+ '--volume': [],
+ 'stdin_open': False,
+ }
+
+ expected_container_options = {
+ 'command': command,
+ 'tty': False,
+ 'stdin_open': False,
+ 'detach': detach,
+ 'entrypoint': 'bash',
+ 'environment': {'MYVAR': 'MYVALUE'},
+ 'labels': {'MYLABEL': ''},
+ 'name': 'MYNAME',
+ 'ports': [],
+ 'restart': None,
+ 'user': 'MYUSER',
+ 'working_dir': '.',
+ }
+
+ container_options = build_one_off_container_options(options, detach, command)
+ assert container_options == expected_container_options
+
+ def test_get_docker_start_call(self):
+ container_id = 'my_container_id'
+
+ mock_container_options = {'detach': False, 'stdin_open': True}
+ expected_docker_start_call = ['start', '--attach', '--interactive', container_id]
+ docker_start_call = get_docker_start_call(mock_container_options, container_id)
+ assert expected_docker_start_call == docker_start_call
+
+ mock_container_options = {'detach': False, 'stdin_open': False}
+ expected_docker_start_call = ['start', '--attach', container_id]
+ docker_start_call = get_docker_start_call(mock_container_options, container_id)
+ assert expected_docker_start_call == docker_start_call
+
+ mock_container_options = {'detach': True, 'stdin_open': True}
+ expected_docker_start_call = ['start', '--interactive', container_id]
+ docker_start_call = get_docker_start_call(mock_container_options, container_id)
+ assert expected_docker_start_call == docker_start_call
+
+ mock_container_options = {'detach': True, 'stdin_open': False}
+ expected_docker_start_call = ['start', container_id]
+ docker_start_call = get_docker_start_call(mock_container_options, container_id)
+ assert expected_docker_start_call == docker_start_call
class TestSetupConsoleHandlerTestCase(object):
@@ -123,13 +183,13 @@ def mock_find_executable(exe):
class TestCallDocker(object):
def test_simple_no_options(self):
with mock.patch('subprocess.call') as fake_call:
- call_docker(['ps'], {})
+ call_docker(['ps'], {}, {})
assert fake_call.call_args[0][0] == ['docker', 'ps']
def test_simple_tls_option(self):
with mock.patch('subprocess.call') as fake_call:
- call_docker(['ps'], {'--tls': True})
+ call_docker(['ps'], {'--tls': True}, {})
assert fake_call.call_args[0][0] == ['docker', '--tls', 'ps']
@@ -140,7 +200,7 @@ class TestCallDocker(object):
'--tlscacert': './ca.pem',
'--tlscert': './cert.pem',
'--tlskey': './key.pem',
- })
+ }, {})
assert fake_call.call_args[0][0] == [
'docker', '--tls', '--tlscacert', './ca.pem', '--tlscert',
@@ -149,7 +209,7 @@ class TestCallDocker(object):
def test_with_host_option(self):
with mock.patch('subprocess.call') as fake_call:
- call_docker(['ps'], {'--host': 'tcp://mydocker.net:2333'})
+ call_docker(['ps'], {'--host': 'tcp://mydocker.net:2333'}, {})
assert fake_call.call_args[0][0] == [
'docker', '--host', 'tcp://mydocker.net:2333', 'ps'
@@ -157,7 +217,7 @@ class TestCallDocker(object):
def test_with_http_host(self):
with mock.patch('subprocess.call') as fake_call:
- call_docker(['ps'], {'--host': 'http://mydocker.net:2333'})
+ call_docker(['ps'], {'--host': 'http://mydocker.net:2333'}, {})
assert fake_call.call_args[0][0] == [
'docker', '--host', 'tcp://mydocker.net:2333', 'ps',
@@ -165,8 +225,17 @@ class TestCallDocker(object):
def test_with_host_option_shorthand_equal(self):
with mock.patch('subprocess.call') as fake_call:
- call_docker(['ps'], {'--host': '=tcp://mydocker.net:2333'})
+ call_docker(['ps'], {'--host': '=tcp://mydocker.net:2333'}, {})
assert fake_call.call_args[0][0] == [
'docker', '--host', 'tcp://mydocker.net:2333', 'ps'
]
+
+ def test_with_env(self):
+ with mock.patch('subprocess.call') as fake_call:
+ call_docker(['ps'], {}, {'DOCKER_HOST': 'tcp://mydocker.net:2333'})
+
+ assert fake_call.call_args[0][0] == [
+ 'docker', 'ps'
+ ]
+ assert fake_call.call_args[1]['env'] == {'DOCKER_HOST': 'tcp://mydocker.net:2333'}
diff --git a/tests/unit/cli/utils_test.py b/tests/unit/cli/utils_test.py
index 26524ff3..b340fb94 100644
--- a/tests/unit/cli/utils_test.py
+++ b/tests/unit/cli/utils_test.py
@@ -3,6 +3,7 @@ from __future__ import unicode_literals
import unittest
+from compose.cli.utils import human_readable_file_size
from compose.utils import unquote_path
@@ -21,3 +22,23 @@ class UnquotePathTest(unittest.TestCase):
assert unquote_path('""hello""') == '"hello"'
assert unquote_path('"hel"lo"') == 'hel"lo'
assert unquote_path('"hello""') == 'hello"'
+
+
+class HumanReadableFileSizeTest(unittest.TestCase):
+ def test_100b(self):
+ assert human_readable_file_size(100) == '100 B'
+
+ def test_1kb(self):
+ assert human_readable_file_size(1024) == '1 kB'
+
+ def test_1023b(self):
+ assert human_readable_file_size(1023) == '1023 B'
+
+ def test_units(self):
+ assert human_readable_file_size((2 ** 10) ** 0) == '1 B'
+ assert human_readable_file_size((2 ** 10) ** 1) == '1 kB'
+ assert human_readable_file_size((2 ** 10) ** 2) == '1 MB'
+ assert human_readable_file_size((2 ** 10) ** 3) == '1 GB'
+ assert human_readable_file_size((2 ** 10) ** 4) == '1 TB'
+ assert human_readable_file_size((2 ** 10) ** 5) == '1 PB'
+ assert human_readable_file_size((2 ** 10) ** 6) == '1 EB'
diff --git a/tests/unit/config/config_test.py b/tests/unit/config/config_test.py
index 50d8e13a..b583422f 100644
--- a/tests/unit/config/config_test.py
+++ b/tests/unit/config/config_test.py
@@ -15,6 +15,7 @@ import pytest
import yaml
from ...helpers import build_config_details
+from ...helpers import BUSYBOX_IMAGE_WITH_TAG
from compose.config import config
from compose.config import types
from compose.config.config import resolve_build_args
@@ -329,7 +330,7 @@ class ConfigTest(unittest.TestCase):
)
assert 'Unexpected type for "version" key in "filename.yml"' \
- in mock_logging.warn.call_args[0][0]
+ in mock_logging.warning.call_args[0][0]
service_dicts = config_data.services
assert service_sort(service_dicts) == service_sort([
@@ -343,7 +344,7 @@ class ConfigTest(unittest.TestCase):
with pytest.raises(ConfigurationError):
config.load(
build_config_details(
- {'web': 'busybox:latest'},
+ {'web': BUSYBOX_IMAGE_WITH_TAG},
'working_dir',
'filename.yml'
)
@@ -353,7 +354,7 @@ class ConfigTest(unittest.TestCase):
with pytest.raises(ConfigurationError):
config.load(
build_config_details(
- {'version': '2', 'services': {'web': 'busybox:latest'}},
+ {'version': '2', 'services': {'web': BUSYBOX_IMAGE_WITH_TAG}},
'working_dir',
'filename.yml'
)
@@ -364,7 +365,7 @@ class ConfigTest(unittest.TestCase):
config.load(
build_config_details({
'version': '2',
- 'services': {'web': 'busybox:latest'},
+ 'services': {'web': BUSYBOX_IMAGE_WITH_TAG},
'networks': {
'invalid': {'foo', 'bar'}
}
@@ -613,6 +614,25 @@ class ConfigTest(unittest.TestCase):
excinfo.exconly()
)
+ def test_config_integer_service_name_raise_validation_error_v2_when_no_interpolate(self):
+ with pytest.raises(ConfigurationError) as excinfo:
+ config.load(
+ build_config_details(
+ {
+ 'version': '2',
+ 'services': {1: {'image': 'busybox'}}
+ },
+ 'working_dir',
+ 'filename.yml'
+ ),
+ interpolate=False
+ )
+
+ assert (
+ "In file 'filename.yml', the service name 1 must be a quoted string, i.e. '1'." in
+ excinfo.exconly()
+ )
+
def test_config_integer_service_property_raise_validation_error(self):
with pytest.raises(ConfigurationError) as excinfo:
config.load(
@@ -828,15 +848,15 @@ class ConfigTest(unittest.TestCase):
def test_load_sorts_in_dependency_order(self):
config_details = build_config_details({
'web': {
- 'image': 'busybox:latest',
+ 'image': BUSYBOX_IMAGE_WITH_TAG,
'links': ['db'],
},
'db': {
- 'image': 'busybox:latest',
+ 'image': BUSYBOX_IMAGE_WITH_TAG,
'volumes_from': ['volume:ro']
},
'volume': {
- 'image': 'busybox:latest',
+ 'image': BUSYBOX_IMAGE_WITH_TAG,
'volumes': ['/tmp'],
}
})
@@ -1261,7 +1281,7 @@ class ConfigTest(unittest.TestCase):
'version': '2',
'services': {
'web': {
- 'image': 'busybox:latest',
+ 'image': BUSYBOX_IMAGE_WITH_TAG,
'volumes': ['data0028:/data:ro'],
},
},
@@ -1277,7 +1297,7 @@ class ConfigTest(unittest.TestCase):
'version': '2',
'services': {
'web': {
- 'image': 'busybox:latest',
+ 'image': BUSYBOX_IMAGE_WITH_TAG,
'volumes': ['./data0028:/data:ro'],
},
},
@@ -1293,7 +1313,7 @@ class ConfigTest(unittest.TestCase):
'base.yaml',
{
'web': {
- 'image': 'busybox:latest',
+ 'image': BUSYBOX_IMAGE_WITH_TAG,
'volumes': ['data0028:/data:ro'],
},
}
@@ -1310,7 +1330,7 @@ class ConfigTest(unittest.TestCase):
'version': '2.3',
'services': {
'web': {
- 'image': 'busybox:latest',
+ 'image': BUSYBOX_IMAGE_WITH_TAG,
'volumes': [
{
'target': '/anonymous', 'type': 'volume'
@@ -1355,7 +1375,7 @@ class ConfigTest(unittest.TestCase):
'version': '3.4',
'services': {
'web': {
- 'image': 'busybox:latest',
+ 'image': BUSYBOX_IMAGE_WITH_TAG,
'volumes': [
{'type': 'bind', 'source': './web', 'target': '/web'},
],
@@ -1377,7 +1397,7 @@ class ConfigTest(unittest.TestCase):
'version': '3.4',
'services': {
'web': {
- 'image': 'busybox:latest',
+ 'image': BUSYBOX_IMAGE_WITH_TAG,
'volumes': [
{'type': 'bind', 'source': '~/web', 'target': '/web'},
],
@@ -2274,7 +2294,7 @@ class ConfigTest(unittest.TestCase):
def test_merge_mixed_ports(self):
base = {
- 'image': 'busybox:latest',
+ 'image': BUSYBOX_IMAGE_WITH_TAG,
'command': 'top',
'ports': [
{
@@ -2291,7 +2311,7 @@ class ConfigTest(unittest.TestCase):
actual = config.merge_service_dicts(base, override, V3_1)
assert actual == {
- 'image': 'busybox:latest',
+ 'image': BUSYBOX_IMAGE_WITH_TAG,
'command': 'top',
'ports': [types.ServicePort('1245', '1245', 'udp', None, None)]
}
@@ -3466,6 +3486,25 @@ class InterpolationTest(unittest.TestCase):
}
@mock.patch.dict(os.environ)
+ def test_config_file_with_options_environment_file(self):
+ project_dir = 'tests/fixtures/default-env-file'
+ service_dicts = config.load(
+ config.find(
+ project_dir, None, Environment.from_env_file(project_dir, '.env2')
+ )
+ ).services
+
+ assert service_dicts[0] == {
+ 'name': 'web',
+ 'image': 'alpine:latest',
+ 'ports': [
+ types.ServicePort.parse('5644')[0],
+ types.ServicePort.parse('9998')[0]
+ ],
+ 'command': 'false'
+ }
+
+ @mock.patch.dict(os.environ)
def test_config_file_with_environment_variable(self):
project_dir = 'tests/fixtures/environment-interpolation'
os.environ.update(
@@ -3532,8 +3571,8 @@ class InterpolationTest(unittest.TestCase):
with mock.patch('compose.config.environment.log') as log:
config.load(config_details)
- assert 2 == log.warn.call_count
- warnings = sorted(args[0][0] for args in log.warn.call_args_list)
+ assert 2 == log.warning.call_count
+ warnings = sorted(args[0][0] for args in log.warning.call_args_list)
assert 'BAR' in warnings[0]
assert 'FOO' in warnings[1]
@@ -3563,8 +3602,8 @@ class InterpolationTest(unittest.TestCase):
with mock.patch('compose.config.config.log') as log:
config.load(config_details, compatibility=True)
- assert log.warn.call_count == 1
- warn_message = log.warn.call_args[0][0]
+ assert log.warning.call_count == 1
+ warn_message = log.warning.call_args[0][0]
assert warn_message.startswith(
'The following deploy sub-keys are not supported in compatibility mode'
)
@@ -3603,7 +3642,7 @@ class InterpolationTest(unittest.TestCase):
with mock.patch('compose.config.config.log') as log:
cfg = config.load(config_details, compatibility=True)
- assert log.warn.call_count == 0
+ assert log.warning.call_count == 0
service_dict = cfg.services[0]
assert service_dict == {
@@ -3783,35 +3822,35 @@ class MergePathMappingTest(object):
{self.config_name: ['/foo:/code', '/data']},
{},
DEFAULT_VERSION)
- assert set(service_dict[self.config_name]) == set(['/foo:/code', '/data'])
+ assert set(service_dict[self.config_name]) == {'/foo:/code', '/data'}
def test_no_base(self):
service_dict = config.merge_service_dicts(
{},
{self.config_name: ['/bar:/code']},
DEFAULT_VERSION)
- assert set(service_dict[self.config_name]) == set(['/bar:/code'])
+ assert set(service_dict[self.config_name]) == {'/bar:/code'}
def test_override_explicit_path(self):
service_dict = config.merge_service_dicts(
{self.config_name: ['/foo:/code', '/data']},
{self.config_name: ['/bar:/code']},
DEFAULT_VERSION)
- assert set(service_dict[self.config_name]) == set(['/bar:/code', '/data'])
+ assert set(service_dict[self.config_name]) == {'/bar:/code', '/data'}
def test_add_explicit_path(self):
service_dict = config.merge_service_dicts(
{self.config_name: ['/foo:/code', '/data']},
{self.config_name: ['/bar:/code', '/quux:/data']},
DEFAULT_VERSION)
- assert set(service_dict[self.config_name]) == set(['/bar:/code', '/quux:/data'])
+ assert set(service_dict[self.config_name]) == {'/bar:/code', '/quux:/data'}
def test_remove_explicit_path(self):
service_dict = config.merge_service_dicts(
{self.config_name: ['/foo:/code', '/quux:/data']},
{self.config_name: ['/bar:/code', '/data']},
DEFAULT_VERSION)
- assert set(service_dict[self.config_name]) == set(['/bar:/code', '/data'])
+ assert set(service_dict[self.config_name]) == {'/bar:/code', '/data'}
class MergeVolumesTest(unittest.TestCase, MergePathMappingTest):
@@ -4015,28 +4054,28 @@ class MergeStringsOrListsTest(unittest.TestCase):
{'dns': '8.8.8.8'},
{},
DEFAULT_VERSION)
- assert set(service_dict['dns']) == set(['8.8.8.8'])
+ assert set(service_dict['dns']) == {'8.8.8.8'}
def test_no_base(self):
service_dict = config.merge_service_dicts(
{},
{'dns': '8.8.8.8'},
DEFAULT_VERSION)
- assert set(service_dict['dns']) == set(['8.8.8.8'])
+ assert set(service_dict['dns']) == {'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'},
DEFAULT_VERSION)
- assert set(service_dict['dns']) == set(['8.8.8.8', '9.9.9.9'])
+ assert set(service_dict['dns']) == {'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']},
DEFAULT_VERSION)
- assert set(service_dict['dns']) == set(['8.8.8.8', '9.9.9.9'])
+ assert set(service_dict['dns']) == {'8.8.8.8', '9.9.9.9'}
class MergeLabelsTest(unittest.TestCase):
@@ -4108,7 +4147,7 @@ class MergeBuildTest(unittest.TestCase):
assert result['context'] == override['context']
assert result['dockerfile'] == override['dockerfile']
assert result['args'] == {'x': '12', 'y': '2'}
- assert set(result['cache_from']) == set(['ubuntu', 'debian'])
+ assert set(result['cache_from']) == {'ubuntu', 'debian'}
assert result['labels'] == override['labels']
def test_empty_override(self):
@@ -4312,7 +4351,7 @@ class EnvTest(unittest.TestCase):
"tests/fixtures/env",
)
).services[0]
- assert set(service_dict['volumes']) == set([VolumeSpec.parse('/tmp:/host/tmp')])
+ assert set(service_dict['volumes']) == {VolumeSpec.parse('/tmp:/host/tmp')}
service_dict = config.load(
build_config_details(
@@ -4320,7 +4359,7 @@ class EnvTest(unittest.TestCase):
"tests/fixtures/env",
)
).services[0]
- assert set(service_dict['volumes']) == set([VolumeSpec.parse('/opt/tmp:/opt/host/tmp')])
+ assert set(service_dict['volumes']) == {VolumeSpec.parse('/opt/tmp:/opt/host/tmp')}
def load_from_filename(filename, override_dir=None):
@@ -5327,6 +5366,28 @@ class SerializeTest(unittest.TestCase):
assert serialized_service['command'] == 'echo $$FOO'
assert serialized_service['entrypoint'][0] == '$$SHELL'
+ def test_serialize_escape_dont_interpolate(self):
+ cfg = {
+ 'version': '2.2',
+ 'services': {
+ 'web': {
+ 'image': 'busybox',
+ 'command': 'echo $FOO',
+ 'environment': {
+ 'CURRENCY': '$'
+ },
+ 'entrypoint': ['$SHELL', '-c'],
+ }
+ }
+ }
+ config_dict = config.load(build_config_details(cfg), interpolate=False)
+
+ serialized_config = yaml.load(serialize_config(config_dict, escape_dollar=False))
+ serialized_service = serialized_config['services']['web']
+ assert serialized_service['environment']['CURRENCY'] == '$'
+ assert serialized_service['command'] == 'echo $FOO'
+ assert serialized_service['entrypoint'][0] == '$SHELL'
+
def test_serialize_unicode_values(self):
cfg = {
'version': '2.3',
diff --git a/tests/unit/container_test.py b/tests/unit/container_test.py
index fde17847..626b466d 100644
--- a/tests/unit/container_test.py
+++ b/tests/unit/container_test.py
@@ -5,6 +5,7 @@ import docker
from .. import mock
from .. import unittest
+from ..helpers import BUSYBOX_IMAGE_WITH_TAG
from compose.const import LABEL_ONE_OFF
from compose.const import LABEL_SLUG
from compose.container import Container
@@ -17,7 +18,7 @@ class ContainerTest(unittest.TestCase):
self.container_id = "abcabcabcbabc12345"
self.container_dict = {
"Id": self.container_id,
- "Image": "busybox:latest",
+ "Image": BUSYBOX_IMAGE_WITH_TAG,
"Command": "top",
"Created": 1387384730,
"Status": "Up 8 seconds",
@@ -43,7 +44,7 @@ class ContainerTest(unittest.TestCase):
has_been_inspected=True)
assert container.dictionary == {
"Id": self.container_id,
- "Image": "busybox:latest",
+ "Image": BUSYBOX_IMAGE_WITH_TAG,
"Name": "/composetest_db_1",
}
@@ -58,7 +59,7 @@ class ContainerTest(unittest.TestCase):
has_been_inspected=True)
assert container.dictionary == {
"Id": self.container_id,
- "Image": "busybox:latest",
+ "Image": BUSYBOX_IMAGE_WITH_TAG,
"Name": "/composetest_db_1",
}
diff --git a/tests/unit/network_test.py b/tests/unit/network_test.py
index d7ffa289..82cfb3be 100644
--- a/tests/unit/network_test.py
+++ b/tests/unit/network_test.py
@@ -165,6 +165,6 @@ class NetworkTest(unittest.TestCase):
with mock.patch('compose.network.log') as mock_log:
check_remote_network_config(remote, net)
- mock_log.warn.assert_called_once_with(mock.ANY)
- _, args, kwargs = mock_log.warn.mock_calls[0]
+ mock_log.warning.assert_called_once_with(mock.ANY)
+ _, args, kwargs = mock_log.warning.mock_calls[0]
assert 'label "com.project.touhou.character" has changed' in args[0]
diff --git a/tests/unit/project_test.py b/tests/unit/project_test.py
index 4aea91a0..93a9aa29 100644
--- a/tests/unit/project_test.py
+++ b/tests/unit/project_test.py
@@ -10,11 +10,14 @@ from docker.errors import NotFound
from .. import mock
from .. import unittest
+from ..helpers import BUSYBOX_IMAGE_WITH_TAG
from compose.config.config import Config
from compose.config.types import VolumeFromSpec
from compose.const import COMPOSEFILE_V1 as V1
from compose.const import COMPOSEFILE_V2_0 as V2_0
from compose.const import COMPOSEFILE_V2_4 as V2_4
+from compose.const import COMPOSEFILE_V3_7 as V3_7
+from compose.const import DEFAULT_TIMEOUT
from compose.const import LABEL_SERVICE
from compose.container import Container
from compose.errors import OperationFailedError
@@ -37,11 +40,11 @@ class ProjectTest(unittest.TestCase):
services=[
{
'name': 'web',
- 'image': 'busybox:latest',
+ 'image': BUSYBOX_IMAGE_WITH_TAG,
},
{
'name': 'db',
- 'image': 'busybox:latest',
+ 'image': BUSYBOX_IMAGE_WITH_TAG,
},
],
networks=None,
@@ -56,9 +59,9 @@ class ProjectTest(unittest.TestCase):
)
assert len(project.services) == 2
assert project.get_service('web').name == 'web'
- assert project.get_service('web').options['image'] == 'busybox:latest'
+ assert project.get_service('web').options['image'] == BUSYBOX_IMAGE_WITH_TAG
assert project.get_service('db').name == 'db'
- assert project.get_service('db').options['image'] == 'busybox:latest'
+ assert project.get_service('db').options['image'] == BUSYBOX_IMAGE_WITH_TAG
assert not project.networks.use_networking
@mock.patch('compose.network.Network.true_name', lambda n: n.full_name)
@@ -68,11 +71,11 @@ class ProjectTest(unittest.TestCase):
services=[
{
'name': 'web',
- 'image': 'busybox:latest',
+ 'image': BUSYBOX_IMAGE_WITH_TAG,
},
{
'name': 'db',
- 'image': 'busybox:latest',
+ 'image': BUSYBOX_IMAGE_WITH_TAG,
},
],
networks=None,
@@ -89,7 +92,7 @@ class ProjectTest(unittest.TestCase):
project='composetest',
name='web',
client=None,
- image="busybox:latest",
+ image=BUSYBOX_IMAGE_WITH_TAG,
)
project = Project('test', [web], None)
assert project.get_service('web') == web
@@ -174,7 +177,7 @@ class ProjectTest(unittest.TestCase):
version=V2_0,
services=[{
'name': 'test',
- 'image': 'busybox:latest',
+ 'image': BUSYBOX_IMAGE_WITH_TAG,
'volumes_from': [VolumeFromSpec('aaa', 'rw', 'container')]
}],
networks=None,
@@ -192,7 +195,7 @@ class ProjectTest(unittest.TestCase):
"Name": container_name,
"Names": [container_name],
"Id": container_name,
- "Image": 'busybox:latest'
+ "Image": BUSYBOX_IMAGE_WITH_TAG
}
]
project = Project.from_config(
@@ -203,11 +206,11 @@ class ProjectTest(unittest.TestCase):
services=[
{
'name': 'vol',
- 'image': 'busybox:latest'
+ 'image': BUSYBOX_IMAGE_WITH_TAG
},
{
'name': 'test',
- 'image': 'busybox:latest',
+ 'image': BUSYBOX_IMAGE_WITH_TAG,
'volumes_from': [VolumeFromSpec('vol', 'rw', 'service')]
}
],
@@ -231,11 +234,11 @@ class ProjectTest(unittest.TestCase):
services=[
{
'name': 'vol',
- 'image': 'busybox:latest'
+ 'image': BUSYBOX_IMAGE_WITH_TAG
},
{
'name': 'test',
- 'image': 'busybox:latest',
+ 'image': BUSYBOX_IMAGE_WITH_TAG,
'volumes_from': [VolumeFromSpec('vol', 'rw', 'service')]
}
],
@@ -541,7 +544,7 @@ class ProjectTest(unittest.TestCase):
services=[
{
'name': 'test',
- 'image': 'busybox:latest',
+ 'image': BUSYBOX_IMAGE_WITH_TAG,
}
],
networks=None,
@@ -566,7 +569,7 @@ class ProjectTest(unittest.TestCase):
services=[
{
'name': 'test',
- 'image': 'busybox:latest',
+ 'image': BUSYBOX_IMAGE_WITH_TAG,
'network_mode': 'container:aaa'
},
],
@@ -586,7 +589,7 @@ class ProjectTest(unittest.TestCase):
"Name": container_name,
"Names": [container_name],
"Id": container_name,
- "Image": 'busybox:latest'
+ "Image": BUSYBOX_IMAGE_WITH_TAG
}
]
project = Project.from_config(
@@ -597,11 +600,11 @@ class ProjectTest(unittest.TestCase):
services=[
{
'name': 'aaa',
- 'image': 'busybox:latest'
+ 'image': BUSYBOX_IMAGE_WITH_TAG
},
{
'name': 'test',
- 'image': 'busybox:latest',
+ 'image': BUSYBOX_IMAGE_WITH_TAG,
'network_mode': 'service:aaa'
},
],
@@ -624,7 +627,7 @@ class ProjectTest(unittest.TestCase):
services=[
{
'name': 'foo',
- 'image': 'busybox:latest'
+ 'image': BUSYBOX_IMAGE_WITH_TAG
},
],
networks=None,
@@ -645,7 +648,7 @@ class ProjectTest(unittest.TestCase):
services=[
{
'name': 'foo',
- 'image': 'busybox:latest',
+ 'image': BUSYBOX_IMAGE_WITH_TAG,
'networks': {'custom': None}
},
],
@@ -660,9 +663,9 @@ class ProjectTest(unittest.TestCase):
def test_container_without_name(self):
self.mock_client.containers.return_value = [
- {'Image': 'busybox:latest', 'Id': '1', 'Name': '1'},
- {'Image': 'busybox:latest', 'Id': '2', 'Name': None},
- {'Image': 'busybox:latest', 'Id': '3'},
+ {'Image': BUSYBOX_IMAGE_WITH_TAG, 'Id': '1', 'Name': '1'},
+ {'Image': BUSYBOX_IMAGE_WITH_TAG, 'Id': '2', 'Name': None},
+ {'Image': BUSYBOX_IMAGE_WITH_TAG, 'Id': '3'},
]
self.mock_client.inspect_container.return_value = {
'Id': '1',
@@ -679,7 +682,7 @@ class ProjectTest(unittest.TestCase):
version=V2_0,
services=[{
'name': 'web',
- 'image': 'busybox:latest',
+ 'image': BUSYBOX_IMAGE_WITH_TAG,
}],
networks=None,
volumes=None,
@@ -697,7 +700,7 @@ class ProjectTest(unittest.TestCase):
version=V2_0,
services=[{
'name': 'web',
- 'image': 'busybox:latest',
+ 'image': BUSYBOX_IMAGE_WITH_TAG,
}],
networks={'default': {}},
volumes={'data': {}},
@@ -709,7 +712,7 @@ class ProjectTest(unittest.TestCase):
self.mock_client.remove_volume.side_effect = NotFound(None, None, 'oops')
project.down(ImageType.all, True)
- self.mock_client.remove_image.assert_called_once_with("busybox:latest")
+ self.mock_client.remove_image.assert_called_once_with(BUSYBOX_IMAGE_WITH_TAG)
def test_no_warning_on_stop(self):
self.mock_client.info.return_value = {'Swarm': {'LocalNodeState': 'active'}}
@@ -742,7 +745,7 @@ class ProjectTest(unittest.TestCase):
def test_project_platform_value(self):
service_config = {
'name': 'web',
- 'image': 'busybox:latest',
+ 'image': BUSYBOX_IMAGE_WITH_TAG,
}
config_data = Config(
version=V2_4, services=[service_config], networks={}, volumes={}, secrets=None, configs=None
@@ -765,6 +768,34 @@ class ProjectTest(unittest.TestCase):
)
assert project.get_service('web').platform == 'linux/s390x'
+ def test_build_container_operation_with_timeout_func_does_not_mutate_options_with_timeout(self):
+ config_data = Config(
+ version=V3_7,
+ services=[
+ {'name': 'web', 'image': BUSYBOX_IMAGE_WITH_TAG},
+ {'name': 'db', 'image': BUSYBOX_IMAGE_WITH_TAG, 'stop_grace_period': '1s'},
+ ],
+ networks={}, volumes={}, secrets=None, configs=None,
+ )
+
+ project = Project.from_config(name='test', client=self.mock_client, config_data=config_data)
+
+ stop_op = project.build_container_operation_with_timeout_func('stop', options={})
+
+ web_container = mock.create_autospec(Container, service='web')
+ db_container = mock.create_autospec(Container, service='db')
+
+ # `stop_grace_period` is not set to 'web' service,
+ # then it is stopped with the default timeout.
+ stop_op(web_container)
+ web_container.stop.assert_called_once_with(timeout=DEFAULT_TIMEOUT)
+
+ # `stop_grace_period` is set to 'db' service,
+ # then it is stopped with the specified timeout and
+ # the value is not overridden by the previous function call.
+ stop_op(db_container)
+ db_container.stop.assert_called_once_with(timeout=1)
+
@mock.patch('compose.parallel.ParallelStreamWriter._write_noansi')
def test_error_parallel_pull(self, mock_write):
project = Project.from_config(
@@ -774,7 +805,7 @@ class ProjectTest(unittest.TestCase):
version=V2_0,
services=[{
'name': 'web',
- 'image': 'busybox:latest',
+ 'image': BUSYBOX_IMAGE_WITH_TAG,
}],
networks=None,
volumes=None,
diff --git a/tests/unit/service_test.py b/tests/unit/service_test.py
index 8b3352fc..a6a633db 100644
--- a/tests/unit/service_test.py
+++ b/tests/unit/service_test.py
@@ -333,7 +333,7 @@ class ServiceTest(unittest.TestCase):
assert service.options['environment'] == environment
assert opts['labels'][LABEL_CONFIG_HASH] == \
- '2524a06fcb3d781aa2c981fc40bcfa08013bb318e4273bfa388df22023e6f2aa'
+ '689149e6041a85f6fb4945a2146a497ed43c8a5cbd8991753d875b165f1b4de4'
assert opts['environment'] == ['also=real']
def test_get_container_create_options_sets_affinity_with_binds(self):
@@ -516,8 +516,8 @@ class ServiceTest(unittest.TestCase):
with mock.patch('compose.service.log', autospec=True) as mock_log:
service.create_container()
- assert mock_log.warn.called
- _, args, _ = mock_log.warn.mock_calls[0]
+ assert mock_log.warning.called
+ _, args, _ = mock_log.warning.mock_calls[0]
assert 'was built because it did not already exist' in args[0]
assert self.mock_client.build.call_count == 1
@@ -546,7 +546,7 @@ class ServiceTest(unittest.TestCase):
with mock.patch('compose.service.log', autospec=True) as mock_log:
service.ensure_image_exists(do_build=BuildAction.force)
- assert not mock_log.warn.called
+ assert not mock_log.warning.called
assert self.mock_client.build.call_count == 1
self.mock_client.build.call_args[1]['tag'] == 'default_foo'
@@ -676,6 +676,7 @@ class ServiceTest(unittest.TestCase):
'options': {'image': 'example.com/foo'},
'links': [('one', 'one')],
'net': 'other',
+ 'secrets': [],
'networks': {'default': None},
'volumes_from': [('two', 'rw')],
}
@@ -698,6 +699,7 @@ class ServiceTest(unittest.TestCase):
'options': {'image': 'example.com/foo'},
'links': [],
'networks': {},
+ 'secrets': [],
'net': 'aaabbb',
'volumes_from': [],
}
@@ -826,7 +828,7 @@ class ServiceTest(unittest.TestCase):
assert service.specifies_host_port()
def test_image_name_from_config(self):
- image_name = 'example/web:latest'
+ image_name = 'example/web:mytag'
service = Service('foo', image=image_name)
assert service.image_name == image_name
@@ -845,13 +847,13 @@ class ServiceTest(unittest.TestCase):
ports=["8080:80"])
service.scale(0)
- assert not mock_log.warn.called
+ assert not mock_log.warning.called
service.scale(1)
- assert not mock_log.warn.called
+ assert not mock_log.warning.called
service.scale(2)
- mock_log.warn.assert_called_once_with(
+ mock_log.warning.assert_called_once_with(
'The "{}" service specifies a port on the host. If multiple containers '
'for this service are created on a single host, the port will clash.'.format(name))
@@ -1332,10 +1334,8 @@ class ServiceVolumesTest(unittest.TestCase):
number=1,
)
- assert set(self.mock_client.create_host_config.call_args[1]['binds']) == set([
- '/host/path:/data1:rw',
- '/host/path:/data2:rw',
- ])
+ assert set(self.mock_client.create_host_config.call_args[1]['binds']) == {'/host/path:/data1:rw',
+ '/host/path:/data2:rw'}
def test_get_container_create_options_with_different_host_path_in_container_json(self):
service = Service(
@@ -1389,7 +1389,7 @@ class ServiceVolumesTest(unittest.TestCase):
with mock.patch('compose.service.log', autospec=True) as mock_log:
warn_on_masked_volume(volumes_option, container_volumes, service)
- assert not mock_log.warn.called
+ assert not mock_log.warning.called
def test_warn_on_masked_volume_when_masked(self):
volumes_option = [VolumeSpec('/home/user', '/path', 'rw')]
@@ -1402,7 +1402,7 @@ class ServiceVolumesTest(unittest.TestCase):
with mock.patch('compose.service.log', autospec=True) as mock_log:
warn_on_masked_volume(volumes_option, container_volumes, service)
- mock_log.warn.assert_called_once_with(mock.ANY)
+ mock_log.warning.assert_called_once_with(mock.ANY)
def test_warn_on_masked_no_warning_with_same_path(self):
volumes_option = [VolumeSpec('/home/user', '/path', 'rw')]
@@ -1412,7 +1412,7 @@ class ServiceVolumesTest(unittest.TestCase):
with mock.patch('compose.service.log', autospec=True) as mock_log:
warn_on_masked_volume(volumes_option, container_volumes, service)
- assert not mock_log.warn.called
+ assert not mock_log.warning.called
def test_warn_on_masked_no_warning_with_container_only_option(self):
volumes_option = [VolumeSpec(None, '/path', 'rw')]
@@ -1424,7 +1424,7 @@ class ServiceVolumesTest(unittest.TestCase):
with mock.patch('compose.service.log', autospec=True) as mock_log:
warn_on_masked_volume(volumes_option, container_volumes, service)
- assert not mock_log.warn.called
+ assert not mock_log.warning.called
def test_create_with_special_volume_mode(self):
self.mock_client.inspect_image.return_value = {'Id': 'imageid'}
diff --git a/tox.ini b/tox.ini
index 08efd4e6..57e57bc6 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,5 +1,5 @@
[tox]
-envlist = py27,py36,py37,pre-commit
+envlist = py27,py37,pre-commit
[testenv]
usedevelop=True