mirror of
https://github.com/docker/compose.git
synced 2025-07-22 13:14:29 +02:00
commit
7bd4291f90
@ -1,11 +1,13 @@
|
|||||||
*.egg-info
|
*.egg-info
|
||||||
.coverage
|
.coverage
|
||||||
.git
|
.git
|
||||||
|
.github
|
||||||
.tox
|
.tox
|
||||||
build
|
build
|
||||||
|
binaries
|
||||||
coverage-html
|
coverage-html
|
||||||
docs/_site
|
docs/_site
|
||||||
venv
|
*venv
|
||||||
.tox
|
.tox
|
||||||
**/__pycache__
|
**/__pycache__
|
||||||
*.pyc
|
*.pyc
|
||||||
|
18
.gitignore
vendored
18
.gitignore
vendored
@ -1,16 +1,18 @@
|
|||||||
*.egg-info
|
*.egg-info
|
||||||
*.pyc
|
*.pyc
|
||||||
|
*.swo
|
||||||
|
*.swp
|
||||||
|
.cache
|
||||||
.coverage*
|
.coverage*
|
||||||
|
.DS_Store
|
||||||
|
.idea
|
||||||
|
|
||||||
/.tox
|
/.tox
|
||||||
|
/binaries
|
||||||
/build
|
/build
|
||||||
|
/compose/GITSHA
|
||||||
/coverage-html
|
/coverage-html
|
||||||
/dist
|
/dist
|
||||||
/docs/_site
|
/docs/_site
|
||||||
/venv
|
/README.rst
|
||||||
README.rst
|
/*venv
|
||||||
compose/GITSHA
|
|
||||||
*.swo
|
|
||||||
*.swp
|
|
||||||
.DS_Store
|
|
||||||
.cache
|
|
||||||
.idea
|
|
||||||
|
@ -48,9 +48,16 @@ naming scheme accordingly before upgrading.
|
|||||||
the actual exit code even when the watched container isn't the cause of the
|
the actual exit code even when the watched container isn't the cause of the
|
||||||
exit.
|
exit.
|
||||||
|
|
||||||
|
- Fixed an issue that would prevent recreating a service in some cases where
|
||||||
|
a volume would be mapped to the same mountpoint as a volume declared inside
|
||||||
|
the image's Dockerfile.
|
||||||
|
|
||||||
- Fixed a bug that caused hash configuration with multiple networks to be
|
- Fixed a bug that caused hash configuration with multiple networks to be
|
||||||
inconsistent, causing some services to be unnecessarily restarted.
|
inconsistent, causing some services to be unnecessarily restarted.
|
||||||
|
|
||||||
|
- Fixed a bug that would cause failures with variable substitution for services
|
||||||
|
with a name containing one or more dot characters
|
||||||
|
|
||||||
- Fixed a pipe handling issue when using the containerized version of Compose.
|
- Fixed a pipe handling issue when using the containerized version of Compose.
|
||||||
|
|
||||||
- Fixed a bug causing `external: false` entries in the Compose file to be
|
- Fixed a bug causing `external: false` entries in the Compose file to be
|
||||||
|
10
Dockerfile
10
Dockerfile
@ -1,20 +1,14 @@
|
|||||||
|
FROM docker:18.06.1 as docker
|
||||||
FROM python:3.6
|
FROM python:3.6
|
||||||
|
|
||||||
RUN set -ex; \
|
RUN set -ex; \
|
||||||
apt-get update -qq; \
|
apt-get update -qq; \
|
||||||
apt-get install -y \
|
apt-get install -y \
|
||||||
locales \
|
locales \
|
||||||
curl \
|
|
||||||
python-dev \
|
python-dev \
|
||||||
git
|
git
|
||||||
|
|
||||||
RUN curl -fsSL -o dockerbins.tgz "https://download.docker.com/linux/static/stable/x86_64/docker-17.12.0-ce.tgz" && \
|
COPY --from=docker /usr/local/bin/docker /usr/local/bin/docker
|
||||||
SHA256=692e1c72937f6214b1038def84463018d8e320c8eaf8530546c84c2f8f9c767d; \
|
|
||||||
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
|
# Python3 requires a valid locale
|
||||||
RUN echo "en_US.UTF-8 UTF-8" > /etc/locale.gen && locale-gen
|
RUN echo "en_US.UTF-8 UTF-8" > /etc/locale.gen && locale-gen
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
FROM alpine:3.6
|
FROM docker:18.06.1 as docker
|
||||||
|
FROM alpine:3.8
|
||||||
|
|
||||||
ENV GLIBC 2.27-r0
|
ENV GLIBC 2.28-r0
|
||||||
ENV DOCKERBINS_SHA 1270dce1bd7e1838d62ae21d2505d87f16efc1d9074645571daaefdfd0c14054
|
|
||||||
|
|
||||||
RUN apk update && apk add --no-cache openssl ca-certificates curl libgcc && \
|
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 /etc/apk/keys/sgerrand.rsa.pub https://alpine-pkgs.sgerrand.com/sgerrand.rsa.pub && \
|
||||||
@ -10,14 +10,10 @@ RUN apk update && apk add --no-cache openssl ca-certificates curl libgcc && \
|
|||||||
ln -s /lib/libz.so.1 /usr/glibc-compat/lib/ && \
|
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 /lib/libc.musl-x86_64.so.1 /usr/glibc-compat/lib && \
|
||||||
ln -s /usr/lib/libgcc_s.so.1 /usr/glibc-compat/lib && \
|
ln -s /usr/lib/libgcc_s.so.1 /usr/glibc-compat/lib && \
|
||||||
curl -fsSL -o dockerbins.tgz "https://download.docker.com/linux/static/stable/x86_64/docker-17.12.1-ce.tgz" && \
|
rm /etc/apk/keys/sgerrand.rsa.pub glibc-$GLIBC.apk && \
|
||||||
echo "${DOCKERBINS_SHA} 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 /etc/apk/keys/sgerrand.rsa.pub glibc-$GLIBC.apk && \
|
|
||||||
apk del curl
|
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
|
COPY dist/docker-compose-Linux-x86_64 /usr/local/bin/docker-compose
|
||||||
|
|
||||||
ENTRYPOINT ["docker-compose"]
|
ENTRYPOINT ["docker-compose"]
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
from __future__ import absolute_import
|
from __future__ import absolute_import
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
__version__ = '1.23.0-rc2'
|
__version__ = '1.23.0-rc3'
|
||||||
|
@ -48,7 +48,7 @@ def interpolate_environment_variables(version, config, section, environment):
|
|||||||
|
|
||||||
|
|
||||||
def get_config_path(config_key, section, name):
|
def get_config_path(config_key, section, name):
|
||||||
return '{}.{}.{}'.format(section, name, config_key)
|
return '{}/{}/{}'.format(section, name, config_key)
|
||||||
|
|
||||||
|
|
||||||
def interpolate_value(name, config_key, value, section, interpolator):
|
def interpolate_value(name, config_key, value, section, interpolator):
|
||||||
@ -75,7 +75,7 @@ def interpolate_value(name, config_key, value, section, interpolator):
|
|||||||
|
|
||||||
def recursive_interpolate(obj, interpolator, config_path):
|
def recursive_interpolate(obj, interpolator, config_path):
|
||||||
def append(config_path, key):
|
def append(config_path, key):
|
||||||
return '{}.{}'.format(config_path, key)
|
return '{}/{}'.format(config_path, key)
|
||||||
|
|
||||||
if isinstance(obj, six.string_types):
|
if isinstance(obj, six.string_types):
|
||||||
return converter.convert(config_path, interpolator.interpolate(obj))
|
return converter.convert(config_path, interpolator.interpolate(obj))
|
||||||
@ -160,12 +160,12 @@ class UnsetRequiredSubstitution(Exception):
|
|||||||
self.err = custom_err_msg
|
self.err = custom_err_msg
|
||||||
|
|
||||||
|
|
||||||
PATH_JOKER = '[^.]+'
|
PATH_JOKER = '[^/]+'
|
||||||
FULL_JOKER = '.+'
|
FULL_JOKER = '.+'
|
||||||
|
|
||||||
|
|
||||||
def re_path(*args):
|
def re_path(*args):
|
||||||
return re.compile('^{}$'.format('\.'.join(args)))
|
return re.compile('^{}$'.format('/'.join(args)))
|
||||||
|
|
||||||
|
|
||||||
def re_path_basic(section, name):
|
def re_path_basic(section, name):
|
||||||
@ -288,7 +288,7 @@ class ConversionMap(object):
|
|||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
raise ConfigurationError(
|
raise ConfigurationError(
|
||||||
'Error while attempting to convert {} to appropriate type: {}'.format(
|
'Error while attempting to convert {} to appropriate type: {}'.format(
|
||||||
path, e
|
path.replace('/', '.'), e
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
return value
|
return value
|
||||||
|
@ -1489,6 +1489,11 @@ def get_container_data_volumes(container, volumes_option, tmpfs_option, mounts_o
|
|||||||
if not mount.get('Name'):
|
if not mount.get('Name'):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
# Volume (probably an image volume) is overridden by a mount in the service's config
|
||||||
|
# and would cause a duplicate mountpoint error
|
||||||
|
if volume.internal in [m.target for m in mounts_option]:
|
||||||
|
continue
|
||||||
|
|
||||||
# Copy existing volume from old container
|
# Copy existing volume from old container
|
||||||
volume = volume._replace(external=mount['Name'])
|
volume = volume._replace(external=mount['Name'])
|
||||||
volumes.append(volume)
|
volumes.append(volume)
|
||||||
|
@ -2,13 +2,13 @@ backports.ssl-match-hostname==3.5.0.1; python_version < '3'
|
|||||||
cached-property==1.3.0
|
cached-property==1.3.0
|
||||||
certifi==2017.4.17
|
certifi==2017.4.17
|
||||||
chardet==3.0.4
|
chardet==3.0.4
|
||||||
|
colorama==0.4.0; sys_platform == 'win32'
|
||||||
docker==3.5.0
|
docker==3.5.0
|
||||||
docker-pycreds==0.3.0
|
docker-pycreds==0.3.0
|
||||||
dockerpty==0.4.1
|
dockerpty==0.4.1
|
||||||
docopt==0.6.2
|
docopt==0.6.2
|
||||||
enum34==1.1.6; python_version < '3.4'
|
enum34==1.1.6; python_version < '3.4'
|
||||||
functools32==3.2.3.post2; python_version < '3.2'
|
functools32==3.2.3.post2; python_version < '3.2'
|
||||||
git+git://github.com/tartley/colorama.git@bd378c725b45eba0b8e5cc091c3ca76a954c92ff; sys_platform == 'win32'
|
|
||||||
idna==2.5
|
idna==2.5
|
||||||
ipaddress==1.0.18
|
ipaddress==1.0.18
|
||||||
jsonschema==2.6.0
|
jsonschema==2.6.0
|
||||||
|
@ -1,15 +0,0 @@
|
|||||||
FROM python:3.6
|
|
||||||
RUN mkdir -p /src && pip install -U Jinja2==2.10 \
|
|
||||||
PyGithub==1.39 \
|
|
||||||
pypandoc==1.4 \
|
|
||||||
GitPython==2.1.9 \
|
|
||||||
requests==2.18.4 \
|
|
||||||
twine==1.11.0 && \
|
|
||||||
apt-get update && apt-get install -y pandoc
|
|
||||||
|
|
||||||
VOLUME /src/script/release
|
|
||||||
WORKDIR /src
|
|
||||||
COPY . /src
|
|
||||||
RUN python setup.py develop
|
|
||||||
ENTRYPOINT ["python", "script/release/release.py"]
|
|
||||||
CMD ["--help"]
|
|
@ -9,8 +9,7 @@ The following things are required to bring a release to a successful conclusion
|
|||||||
|
|
||||||
### Local Docker engine (Linux Containers)
|
### Local Docker engine (Linux Containers)
|
||||||
|
|
||||||
The release script runs inside a container and builds images that will be part
|
The release script builds images that will be part of the release.
|
||||||
of the release.
|
|
||||||
|
|
||||||
### Docker Hub account
|
### Docker Hub account
|
||||||
|
|
||||||
@ -20,11 +19,9 @@ following repositories:
|
|||||||
- docker/compose
|
- docker/compose
|
||||||
- docker/compose-tests
|
- docker/compose-tests
|
||||||
|
|
||||||
### A local Python environment
|
### Python
|
||||||
|
|
||||||
While most of the release script is running inside a Docker container,
|
The release script is written in Python and requires Python 3.3 at minimum.
|
||||||
fetching local Docker credentials depends on the `docker` Python package
|
|
||||||
being available locally.
|
|
||||||
|
|
||||||
### A Github account and Github API token
|
### A Github account and Github API token
|
||||||
|
|
||||||
@ -59,6 +56,18 @@ Said account needs to be a member of the maintainers group for the
|
|||||||
Moreover, the `~/.pypirc` file should exist on your host and contain the
|
Moreover, the `~/.pypirc` file should exist on your host and contain the
|
||||||
relevant pypi credentials.
|
relevant pypi credentials.
|
||||||
|
|
||||||
|
The following is a sample `.pypirc` provided as a guideline:
|
||||||
|
|
||||||
|
```
|
||||||
|
[distutils]
|
||||||
|
index-servers =
|
||||||
|
pypi
|
||||||
|
|
||||||
|
[pypi]
|
||||||
|
username = user
|
||||||
|
password = pass
|
||||||
|
```
|
||||||
|
|
||||||
## Start a feature release
|
## Start a feature release
|
||||||
|
|
||||||
A feature release is a release that includes all changes present in the
|
A feature release is a release that includes all changes present in the
|
||||||
|
@ -17,6 +17,8 @@ from release.const import NAME
|
|||||||
from release.const import REPO_ROOT
|
from release.const import REPO_ROOT
|
||||||
from release.downloader import BinaryDownloader
|
from release.downloader import BinaryDownloader
|
||||||
from release.images import ImageManager
|
from release.images import ImageManager
|
||||||
|
from release.pypi import check_pypirc
|
||||||
|
from release.pypi import pypi_upload
|
||||||
from release.repository import delete_assets
|
from release.repository import delete_assets
|
||||||
from release.repository import get_contributors
|
from release.repository import get_contributors
|
||||||
from release.repository import Repository
|
from release.repository import Repository
|
||||||
@ -28,8 +30,6 @@ from release.utils import ScriptError
|
|||||||
from release.utils import update_init_py_version
|
from release.utils import update_init_py_version
|
||||||
from release.utils import update_run_sh_version
|
from release.utils import update_run_sh_version
|
||||||
from release.utils import yesno
|
from release.utils import yesno
|
||||||
from requests.exceptions import HTTPError
|
|
||||||
from twine.commands.upload import main as twine_upload
|
|
||||||
|
|
||||||
|
|
||||||
def create_initial_branch(repository, args):
|
def create_initial_branch(repository, args):
|
||||||
@ -170,25 +170,6 @@ def distclean():
|
|||||||
shutil.rmtree(folder, ignore_errors=True)
|
shutil.rmtree(folder, ignore_errors=True)
|
||||||
|
|
||||||
|
|
||||||
def pypi_upload(args):
|
|
||||||
print('Uploading to PyPi')
|
|
||||||
try:
|
|
||||||
rel = args.release.replace('-rc', 'rc')
|
|
||||||
twine_upload([
|
|
||||||
'dist/docker_compose-{}*.whl'.format(rel),
|
|
||||||
'dist/docker-compose-{}*.tar.gz'.format(rel)
|
|
||||||
])
|
|
||||||
except HTTPError as e:
|
|
||||||
if e.response.status_code == 400 and 'File already exists' in e.message:
|
|
||||||
if not args.finalize_resume:
|
|
||||||
raise ScriptError(
|
|
||||||
'Package already uploaded on PyPi.'
|
|
||||||
)
|
|
||||||
print('Skipping PyPi upload - package already uploaded')
|
|
||||||
else:
|
|
||||||
raise ScriptError('Unexpected HTTP error uploading package to PyPi: {}'.format(e))
|
|
||||||
|
|
||||||
|
|
||||||
def resume(args):
|
def resume(args):
|
||||||
try:
|
try:
|
||||||
distclean()
|
distclean()
|
||||||
@ -277,6 +258,7 @@ def start(args):
|
|||||||
def finalize(args):
|
def finalize(args):
|
||||||
distclean()
|
distclean()
|
||||||
try:
|
try:
|
||||||
|
check_pypirc()
|
||||||
repository = Repository(REPO_ROOT, args.repo)
|
repository = Repository(REPO_ROOT, args.repo)
|
||||||
img_manager = ImageManager(args.release)
|
img_manager = ImageManager(args.release)
|
||||||
pr_data = repository.find_release_pr(args.release)
|
pr_data = repository.find_release_pr(args.release)
|
||||||
@ -284,7 +266,7 @@ def finalize(args):
|
|||||||
raise ScriptError('No PR found for {}'.format(args.release))
|
raise ScriptError('No PR found for {}'.format(args.release))
|
||||||
if not check_pr_mergeable(pr_data):
|
if not check_pr_mergeable(pr_data):
|
||||||
raise ScriptError('Can not finalize release with an unmergeable PR')
|
raise ScriptError('Can not finalize release with an unmergeable PR')
|
||||||
if not img_manager.check_images(args.release):
|
if not img_manager.check_images():
|
||||||
raise ScriptError('Missing release image')
|
raise ScriptError('Missing release image')
|
||||||
br_name = branch_name(args.release)
|
br_name = branch_name(args.release)
|
||||||
if not repository.branch_exists(br_name):
|
if not repository.branch_exists(br_name):
|
||||||
|
@ -1,36 +1,13 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
|
|
||||||
docker image inspect compose/release-tool > /dev/null
|
if test -d ${VENV_DIR:-./.release-venv}; then
|
||||||
if test $? -ne 0; then
|
true
|
||||||
docker build -t compose/release-tool -f $(pwd)/script/release/Dockerfile $(pwd)
|
else
|
||||||
|
./script/release/setup-venv.sh
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if test -z $GITHUB_TOKEN; then
|
if test -z "$*"; then
|
||||||
echo "GITHUB_TOKEN environment variable must be set"
|
args="--help"
|
||||||
exit 1
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if test -z $BINTRAY_TOKEN; then
|
${VENV_DIR:-./.release-venv}/bin/python ./script/release/release.py "$@"
|
||||||
echo "BINTRAY_TOKEN environment variable must be set"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
if test -z $(python -c "import docker; print(docker.version)" 2>/dev/null); then
|
|
||||||
echo "This script requires the 'docker' Python package to be installed locally"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
hub_credentials=$(python -c "from docker import auth; cfg = auth.load_config(); print(auth.encode_header(auth.resolve_authconfig(cfg, 'docker.io')).decode('ascii'))")
|
|
||||||
|
|
||||||
docker run -it \
|
|
||||||
-e GITHUB_TOKEN=$GITHUB_TOKEN \
|
|
||||||
-e BINTRAY_TOKEN=$BINTRAY_TOKEN \
|
|
||||||
-e SSH_AUTH_SOCK=$SSH_AUTH_SOCK \
|
|
||||||
-e HUB_CREDENTIALS=$hub_credentials \
|
|
||||||
--mount type=bind,source=$(pwd),target=/src \
|
|
||||||
--mount type=bind,source=$HOME/.gitconfig,target=/root/.gitconfig \
|
|
||||||
--mount type=bind,source=/var/run/docker.sock,target=/var/run/docker.sock \
|
|
||||||
--mount type=bind,source=$HOME/.ssh,target=/root/.ssh \
|
|
||||||
--mount type=bind,source=/tmp,target=/tmp \
|
|
||||||
-v $HOME/.pypirc:/root/.pypirc \
|
|
||||||
compose/release-tool $*
|
|
||||||
|
@ -27,13 +27,12 @@ class ImageManager(object):
|
|||||||
def build_images(self, repository, files):
|
def build_images(self, repository, files):
|
||||||
print("Building release images...")
|
print("Building release images...")
|
||||||
repository.write_git_sha()
|
repository.write_git_sha()
|
||||||
docker_client = docker.APIClient(**docker.utils.kwargs_from_env())
|
|
||||||
distdir = os.path.join(REPO_ROOT, 'dist')
|
distdir = os.path.join(REPO_ROOT, 'dist')
|
||||||
os.makedirs(distdir, exist_ok=True)
|
os.makedirs(distdir, exist_ok=True)
|
||||||
shutil.copy(files['docker-compose-Linux-x86_64'][0], distdir)
|
shutil.copy(files['docker-compose-Linux-x86_64'][0], distdir)
|
||||||
os.chmod(os.path.join(distdir, 'docker-compose-Linux-x86_64'), 0o755)
|
os.chmod(os.path.join(distdir, 'docker-compose-Linux-x86_64'), 0o755)
|
||||||
print('Building docker/compose image')
|
print('Building docker/compose image')
|
||||||
logstream = docker_client.build(
|
logstream = self.docker_client.build(
|
||||||
REPO_ROOT, tag='docker/compose:{}'.format(self.version), dockerfile='Dockerfile.run',
|
REPO_ROOT, tag='docker/compose:{}'.format(self.version), dockerfile='Dockerfile.run',
|
||||||
decode=True
|
decode=True
|
||||||
)
|
)
|
||||||
@ -44,7 +43,7 @@ class ImageManager(object):
|
|||||||
print(chunk['stream'], end='')
|
print(chunk['stream'], end='')
|
||||||
|
|
||||||
print('Building test image (for UCP e2e)')
|
print('Building test image (for UCP e2e)')
|
||||||
logstream = docker_client.build(
|
logstream = self.docker_client.build(
|
||||||
REPO_ROOT, tag='docker-compose-tests:tmp', decode=True
|
REPO_ROOT, tag='docker-compose-tests:tmp', decode=True
|
||||||
)
|
)
|
||||||
for chunk in logstream:
|
for chunk in logstream:
|
||||||
@ -53,13 +52,15 @@ class ImageManager(object):
|
|||||||
if 'stream' in chunk:
|
if 'stream' in chunk:
|
||||||
print(chunk['stream'], end='')
|
print(chunk['stream'], end='')
|
||||||
|
|
||||||
container = docker_client.create_container(
|
container = self.docker_client.create_container(
|
||||||
'docker-compose-tests:tmp', entrypoint='tox'
|
'docker-compose-tests:tmp', entrypoint='tox'
|
||||||
)
|
)
|
||||||
docker_client.commit(container, 'docker/compose-tests', 'latest')
|
self.docker_client.commit(container, 'docker/compose-tests', 'latest')
|
||||||
docker_client.tag('docker/compose-tests:latest', 'docker/compose-tests:{}'.format(self.version))
|
self.docker_client.tag(
|
||||||
docker_client.remove_container(container, force=True)
|
'docker/compose-tests:latest', 'docker/compose-tests:{}'.format(self.version)
|
||||||
docker_client.remove_image('docker-compose-tests:tmp', force=True)
|
)
|
||||||
|
self.docker_client.remove_container(container, force=True)
|
||||||
|
self.docker_client.remove_image('docker-compose-tests:tmp', force=True)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def image_names(self):
|
def image_names(self):
|
||||||
@ -69,23 +70,19 @@ class ImageManager(object):
|
|||||||
'docker/compose:{}'.format(self.version)
|
'docker/compose:{}'.format(self.version)
|
||||||
]
|
]
|
||||||
|
|
||||||
def check_images(self, version):
|
def check_images(self):
|
||||||
docker_client = docker.APIClient(**docker.utils.kwargs_from_env())
|
|
||||||
|
|
||||||
for name in self.image_names:
|
for name in self.image_names:
|
||||||
try:
|
try:
|
||||||
docker_client.inspect_image(name)
|
self.docker_client.inspect_image(name)
|
||||||
except docker.errors.ImageNotFound:
|
except docker.errors.ImageNotFound:
|
||||||
print('Expected image {} was not found'.format(name))
|
print('Expected image {} was not found'.format(name))
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def push_images(self):
|
def push_images(self):
|
||||||
docker_client = docker.APIClient(**docker.utils.kwargs_from_env())
|
|
||||||
|
|
||||||
for name in self.image_names:
|
for name in self.image_names:
|
||||||
print('Pushing {} to Docker Hub'.format(name))
|
print('Pushing {} to Docker Hub'.format(name))
|
||||||
logstream = docker_client.push(name, stream=True, decode=True)
|
logstream = self.docker_client.push(name, stream=True, decode=True)
|
||||||
for chunk in logstream:
|
for chunk in logstream:
|
||||||
if 'status' in chunk:
|
if 'status' in chunk:
|
||||||
print(chunk['status'])
|
print(chunk['status'])
|
||||||
|
44
script/release/release/pypi.py
Normal file
44
script/release/release/pypi.py
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
from __future__ import absolute_import
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from configparser import Error
|
||||||
|
from requests.exceptions import HTTPError
|
||||||
|
from twine.commands.upload import main as twine_upload
|
||||||
|
from twine.utils import get_config
|
||||||
|
|
||||||
|
from .utils import ScriptError
|
||||||
|
|
||||||
|
|
||||||
|
def pypi_upload(args):
|
||||||
|
print('Uploading to PyPi')
|
||||||
|
try:
|
||||||
|
rel = args.release.replace('-rc', 'rc')
|
||||||
|
twine_upload([
|
||||||
|
'dist/docker_compose-{}*.whl'.format(rel),
|
||||||
|
'dist/docker-compose-{}*.tar.gz'.format(rel)
|
||||||
|
])
|
||||||
|
except HTTPError as e:
|
||||||
|
if e.response.status_code == 400 and 'File already exists' in e.message:
|
||||||
|
if not args.finalize_resume:
|
||||||
|
raise ScriptError(
|
||||||
|
'Package already uploaded on PyPi.'
|
||||||
|
)
|
||||||
|
print('Skipping PyPi upload - package already uploaded')
|
||||||
|
else:
|
||||||
|
raise ScriptError('Unexpected HTTP error uploading package to PyPi: {}'.format(e))
|
||||||
|
|
||||||
|
|
||||||
|
def check_pypirc():
|
||||||
|
try:
|
||||||
|
config = get_config()
|
||||||
|
except Error as e:
|
||||||
|
raise ScriptError('Failed to parse .pypirc file: {}'.format(e))
|
||||||
|
|
||||||
|
if config is None:
|
||||||
|
raise ScriptError('Failed to parse .pypirc file')
|
||||||
|
|
||||||
|
if 'pypi' not in config:
|
||||||
|
raise ScriptError('Missing [pypi] section in .pypirc file')
|
||||||
|
|
||||||
|
if not (config['pypi'].get('username') and config['pypi'].get('password')):
|
||||||
|
raise ScriptError('Missing login/password pair for pypi repo')
|
47
script/release/setup-venv.sh
Executable file
47
script/release/setup-venv.sh
Executable file
@ -0,0 +1,47 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
debian_based() { test -f /etc/debian_version; }
|
||||||
|
|
||||||
|
if test -z $VENV_DIR; then
|
||||||
|
VENV_DIR=./.release-venv
|
||||||
|
fi
|
||||||
|
|
||||||
|
if test -z $PYTHONBIN; then
|
||||||
|
PYTHONBIN=$(which python3)
|
||||||
|
if test -z $PYTHONBIN; then
|
||||||
|
PYTHONBIN=$(which python)
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
VERSION=$($PYTHONBIN -c "import sys; print('{}.{}'.format(*sys.version_info[0:2]))")
|
||||||
|
if test $(echo $VERSION | cut -d. -f1) -lt 3; then
|
||||||
|
echo "Python 3.3 or above is required"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if test $(echo $VERSION | cut -d. -f2) -lt 3; then
|
||||||
|
echo "Python 3.3 or above is required"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Debian / Ubuntu workaround:
|
||||||
|
# https://askubuntu.com/questions/879437/ensurepip-is-disabled-in-debian-ubuntu-for-the-system-python
|
||||||
|
if debian_based; then
|
||||||
|
VENV_FLAGS="$VENV_FLAGS --without-pip"
|
||||||
|
fi
|
||||||
|
|
||||||
|
$PYTHONBIN -m venv $VENV_DIR $VENV_FLAGS
|
||||||
|
|
||||||
|
VENV_PYTHONBIN=$VENV_DIR/bin/python
|
||||||
|
|
||||||
|
if debian_based; then
|
||||||
|
curl https://bootstrap.pypa.io/get-pip.py -o $VENV_DIR/get-pip.py
|
||||||
|
$VENV_PYTHONBIN $VENV_DIR/get-pip.py
|
||||||
|
fi
|
||||||
|
|
||||||
|
$VENV_PYTHONBIN -m pip install -U Jinja2==2.10 \
|
||||||
|
PyGithub==1.39 \
|
||||||
|
pypandoc==1.4 \
|
||||||
|
GitPython==2.1.9 \
|
||||||
|
requests==2.18.4 \
|
||||||
|
twine==1.11.0
|
||||||
|
|
||||||
|
$VENV_PYTHONBIN setup.py develop
|
@ -15,7 +15,7 @@
|
|||||||
|
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
VERSION="1.23.0-rc2"
|
VERSION="1.23.0-rc3"
|
||||||
IMAGE="docker/compose:$VERSION"
|
IMAGE="docker/compose:$VERSION"
|
||||||
|
|
||||||
|
|
||||||
|
2
setup.py
2
setup.py
@ -55,7 +55,7 @@ extras_require = {
|
|||||||
':python_version < "3.4"': ['enum34 >= 1.0.4, < 2'],
|
':python_version < "3.4"': ['enum34 >= 1.0.4, < 2'],
|
||||||
':python_version < "3.5"': ['backports.ssl_match_hostname >= 3.5'],
|
':python_version < "3.5"': ['backports.ssl_match_hostname >= 3.5'],
|
||||||
':python_version < "3.3"': ['ipaddress >= 1.0.16'],
|
':python_version < "3.3"': ['ipaddress >= 1.0.16'],
|
||||||
':sys_platform == "win32"': ['colorama >= 0.3.9, < 0.4'],
|
':sys_platform == "win32"': ['colorama >= 0.4, < 0.5'],
|
||||||
'socks': ['PySocks >= 1.5.6, != 1.5.7, < 2'],
|
'socks': ['PySocks >= 1.5.6, != 1.5.7, < 2'],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -425,6 +425,22 @@ class ServiceTest(DockerClientTestCase):
|
|||||||
new_container = service.recreate_container(old_container)
|
new_container = service.recreate_container(old_container)
|
||||||
assert new_container.get_mount('/data')['Source'] == volume_path
|
assert new_container.get_mount('/data')['Source'] == volume_path
|
||||||
|
|
||||||
|
def test_recreate_volume_to_mount(self):
|
||||||
|
# https://github.com/docker/compose/issues/6280
|
||||||
|
service = Service(
|
||||||
|
project='composetest',
|
||||||
|
name='db',
|
||||||
|
client=self.client,
|
||||||
|
build={'context': 'tests/fixtures/dockerfile-with-volume'},
|
||||||
|
volumes=[MountSpec.parse({
|
||||||
|
'type': 'volume',
|
||||||
|
'target': '/data',
|
||||||
|
})]
|
||||||
|
)
|
||||||
|
old_container = create_and_start_container(service)
|
||||||
|
new_container = service.recreate_container(old_container)
|
||||||
|
assert new_container.get_mount('/data')['Source']
|
||||||
|
|
||||||
def test_duplicate_volume_trailing_slash(self):
|
def test_duplicate_volume_trailing_slash(self):
|
||||||
"""
|
"""
|
||||||
When an image specifies a volume, and the Compose file specifies a host path
|
When an image specifies a volume, and the Compose file specifies a host path
|
||||||
|
@ -332,6 +332,37 @@ def test_interpolate_environment_external_resource_convert_types(mock_env):
|
|||||||
assert value == expected
|
assert value == expected
|
||||||
|
|
||||||
|
|
||||||
|
def test_interpolate_service_name_uses_dot(mock_env):
|
||||||
|
entry = {
|
||||||
|
'service.1': {
|
||||||
|
'image': 'busybox',
|
||||||
|
'ulimits': {
|
||||||
|
'nproc': '${POSINT}',
|
||||||
|
'nofile': {
|
||||||
|
'soft': '${POSINT}',
|
||||||
|
'hard': '${DEFAULT:-40000}'
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
expected = {
|
||||||
|
'service.1': {
|
||||||
|
'image': 'busybox',
|
||||||
|
'ulimits': {
|
||||||
|
'nproc': 50,
|
||||||
|
'nofile': {
|
||||||
|
'soft': 50,
|
||||||
|
'hard': 40000
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
value = interpolate_environment_variables(V3_4, entry, 'service', mock_env)
|
||||||
|
assert value == expected
|
||||||
|
|
||||||
|
|
||||||
def test_escaped_interpolation(defaults_interpolator):
|
def test_escaped_interpolation(defaults_interpolator):
|
||||||
assert defaults_interpolator('$${foo}') == '${foo}'
|
assert defaults_interpolator('$${foo}') == '${foo}'
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user