mirror of
https://github.com/docker/compose.git
synced 2025-07-20 12:14:41 +02:00
Merge pull request #2601 from dnephin/dont_build_on_up
Only build as part of `up` if `--build` flag is set
This commit is contained in:
commit
1655be6c5b
@ -25,6 +25,7 @@ from ..const import HTTP_TIMEOUT
|
|||||||
from ..const import IS_WINDOWS_PLATFORM
|
from ..const import IS_WINDOWS_PLATFORM
|
||||||
from ..progress_stream import StreamOutputError
|
from ..progress_stream import StreamOutputError
|
||||||
from ..project import NoSuchService
|
from ..project import NoSuchService
|
||||||
|
from ..service import BuildAction
|
||||||
from ..service import BuildError
|
from ..service import BuildError
|
||||||
from ..service import ConvergenceStrategy
|
from ..service import ConvergenceStrategy
|
||||||
from ..service import ImageType
|
from ..service import ImageType
|
||||||
@ -249,14 +250,15 @@ class TopLevelCommand(DocoptCommand):
|
|||||||
image haven't changed. Incompatible with --no-recreate.
|
image haven't changed. Incompatible with --no-recreate.
|
||||||
--no-recreate If containers already exist, don't recreate them.
|
--no-recreate If containers already exist, don't recreate them.
|
||||||
Incompatible with --force-recreate.
|
Incompatible with --force-recreate.
|
||||||
--no-build Don't build an image, even if it's missing
|
--no-build Don't build an image, even if it's missing.
|
||||||
|
--build Build images before creating containers.
|
||||||
"""
|
"""
|
||||||
service_names = options['SERVICE']
|
service_names = options['SERVICE']
|
||||||
|
|
||||||
project.create(
|
project.create(
|
||||||
service_names=service_names,
|
service_names=service_names,
|
||||||
strategy=convergence_strategy_from_opts(options),
|
strategy=convergence_strategy_from_opts(options),
|
||||||
do_build=not options['--no-build']
|
do_build=build_action_from_opts(options),
|
||||||
)
|
)
|
||||||
|
|
||||||
def down(self, project, options):
|
def down(self, project, options):
|
||||||
@ -699,7 +701,8 @@ class TopLevelCommand(DocoptCommand):
|
|||||||
Incompatible with --no-recreate.
|
Incompatible with --no-recreate.
|
||||||
--no-recreate If containers already exist, don't recreate them.
|
--no-recreate If containers already exist, don't recreate them.
|
||||||
Incompatible with --force-recreate.
|
Incompatible with --force-recreate.
|
||||||
--no-build Don't build an image, even if it's missing
|
--no-build Don't build an image, even if it's missing.
|
||||||
|
--build Build images before starting containers.
|
||||||
--abort-on-container-exit Stops all containers if any container was stopped.
|
--abort-on-container-exit Stops all containers if any container was stopped.
|
||||||
Incompatible with -d.
|
Incompatible with -d.
|
||||||
-t, --timeout TIMEOUT Use this timeout in seconds for container shutdown
|
-t, --timeout TIMEOUT Use this timeout in seconds for container shutdown
|
||||||
@ -721,7 +724,7 @@ class TopLevelCommand(DocoptCommand):
|
|||||||
service_names=service_names,
|
service_names=service_names,
|
||||||
start_deps=start_deps,
|
start_deps=start_deps,
|
||||||
strategy=convergence_strategy_from_opts(options),
|
strategy=convergence_strategy_from_opts(options),
|
||||||
do_build=not options['--no-build'],
|
do_build=build_action_from_opts(options),
|
||||||
timeout=timeout,
|
timeout=timeout,
|
||||||
detached=detached)
|
detached=detached)
|
||||||
|
|
||||||
@ -775,6 +778,19 @@ def image_type_from_opt(flag, value):
|
|||||||
raise UserError("%s flag must be one of: all, local" % flag)
|
raise UserError("%s flag must be one of: all, local" % flag)
|
||||||
|
|
||||||
|
|
||||||
|
def build_action_from_opts(options):
|
||||||
|
if options['--build'] and options['--no-build']:
|
||||||
|
raise UserError("--build and --no-build can not be combined.")
|
||||||
|
|
||||||
|
if options['--build']:
|
||||||
|
return BuildAction.force
|
||||||
|
|
||||||
|
if options['--no-build']:
|
||||||
|
return BuildAction.skip
|
||||||
|
|
||||||
|
return BuildAction.none
|
||||||
|
|
||||||
|
|
||||||
def run_one_off_container(container_options, project, service, options):
|
def run_one_off_container(container_options, project, service, options):
|
||||||
if not options['--no-deps']:
|
if not options['--no-deps']:
|
||||||
deps = service.get_dependency_names()
|
deps = service.get_dependency_names()
|
||||||
|
@ -21,6 +21,7 @@ from .container import Container
|
|||||||
from .network import build_networks
|
from .network import build_networks
|
||||||
from .network import get_networks
|
from .network import get_networks
|
||||||
from .network import ProjectNetworks
|
from .network import ProjectNetworks
|
||||||
|
from .service import BuildAction
|
||||||
from .service import ContainerNetworkMode
|
from .service import ContainerNetworkMode
|
||||||
from .service import ConvergenceStrategy
|
from .service import ConvergenceStrategy
|
||||||
from .service import NetworkMode
|
from .service import NetworkMode
|
||||||
@ -249,7 +250,12 @@ class Project(object):
|
|||||||
else:
|
else:
|
||||||
log.info('%s uses an image, skipping' % service.name)
|
log.info('%s uses an image, skipping' % service.name)
|
||||||
|
|
||||||
def create(self, service_names=None, strategy=ConvergenceStrategy.changed, do_build=True):
|
def create(
|
||||||
|
self,
|
||||||
|
service_names=None,
|
||||||
|
strategy=ConvergenceStrategy.changed,
|
||||||
|
do_build=BuildAction.none,
|
||||||
|
):
|
||||||
services = self.get_services_without_duplicate(service_names, include_deps=True)
|
services = self.get_services_without_duplicate(service_names, include_deps=True)
|
||||||
|
|
||||||
plans = self._get_convergence_plans(services, strategy)
|
plans = self._get_convergence_plans(services, strategy)
|
||||||
@ -298,7 +304,7 @@ class Project(object):
|
|||||||
service_names=None,
|
service_names=None,
|
||||||
start_deps=True,
|
start_deps=True,
|
||||||
strategy=ConvergenceStrategy.changed,
|
strategy=ConvergenceStrategy.changed,
|
||||||
do_build=True,
|
do_build=BuildAction.none,
|
||||||
timeout=DEFAULT_TIMEOUT,
|
timeout=DEFAULT_TIMEOUT,
|
||||||
detached=False):
|
detached=False):
|
||||||
|
|
||||||
|
@ -104,6 +104,14 @@ class ImageType(enum.Enum):
|
|||||||
all = 2
|
all = 2
|
||||||
|
|
||||||
|
|
||||||
|
@enum.unique
|
||||||
|
class BuildAction(enum.Enum):
|
||||||
|
"""Enumeration for the possible build actions."""
|
||||||
|
none = 0
|
||||||
|
force = 1
|
||||||
|
skip = 2
|
||||||
|
|
||||||
|
|
||||||
class Service(object):
|
class Service(object):
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
@ -243,7 +251,7 @@ class Service(object):
|
|||||||
|
|
||||||
def create_container(self,
|
def create_container(self,
|
||||||
one_off=False,
|
one_off=False,
|
||||||
do_build=True,
|
do_build=BuildAction.none,
|
||||||
previous_container=None,
|
previous_container=None,
|
||||||
number=None,
|
number=None,
|
||||||
quiet=False,
|
quiet=False,
|
||||||
@ -266,20 +274,29 @@ class Service(object):
|
|||||||
|
|
||||||
return Container.create(self.client, **container_options)
|
return Container.create(self.client, **container_options)
|
||||||
|
|
||||||
def ensure_image_exists(self, do_build=True):
|
def ensure_image_exists(self, do_build=BuildAction.none):
|
||||||
|
if self.can_be_built() and do_build == BuildAction.force:
|
||||||
|
self.build()
|
||||||
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.image()
|
self.image()
|
||||||
return
|
return
|
||||||
except NoSuchImageError:
|
except NoSuchImageError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if self.can_be_built():
|
if not self.can_be_built():
|
||||||
if do_build:
|
|
||||||
self.build()
|
|
||||||
else:
|
|
||||||
raise NeedsBuildError(self)
|
|
||||||
else:
|
|
||||||
self.pull()
|
self.pull()
|
||||||
|
return
|
||||||
|
|
||||||
|
if do_build == BuildAction.skip:
|
||||||
|
raise NeedsBuildError(self)
|
||||||
|
|
||||||
|
self.build()
|
||||||
|
log.warn(
|
||||||
|
"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 image(self):
|
def image(self):
|
||||||
try:
|
try:
|
||||||
@ -343,7 +360,7 @@ class Service(object):
|
|||||||
|
|
||||||
def execute_convergence_plan(self,
|
def execute_convergence_plan(self,
|
||||||
plan,
|
plan,
|
||||||
do_build=True,
|
do_build=BuildAction.none,
|
||||||
timeout=DEFAULT_TIMEOUT,
|
timeout=DEFAULT_TIMEOUT,
|
||||||
detached=False,
|
detached=False,
|
||||||
start=True):
|
start=True):
|
||||||
@ -392,7 +409,7 @@ class Service(object):
|
|||||||
def recreate_container(
|
def recreate_container(
|
||||||
self,
|
self,
|
||||||
container,
|
container,
|
||||||
do_build=False,
|
do_build=BuildAction.none,
|
||||||
timeout=DEFAULT_TIMEOUT,
|
timeout=DEFAULT_TIMEOUT,
|
||||||
attach_logs=False,
|
attach_logs=False,
|
||||||
start_new_container=True):
|
start_new_container=True):
|
||||||
|
@ -12,14 +12,15 @@ parent = "smn_compose_cli"
|
|||||||
# create
|
# create
|
||||||
|
|
||||||
```
|
```
|
||||||
|
Creates containers for a service.
|
||||||
|
|
||||||
Usage: create [options] [SERVICE...]
|
Usage: create [options] [SERVICE...]
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
--force-recreate Recreate containers even if their configuration and
|
--force-recreate Recreate containers even if their configuration and
|
||||||
image haven't changed. Incompatible with --no-recreate.
|
image haven't changed. Incompatible with --no-recreate.
|
||||||
--no-recreate If containers already exist, don't recreate them.
|
--no-recreate If containers already exist, don't recreate them.
|
||||||
Incompatible with --force-recreate.
|
Incompatible with --force-recreate.
|
||||||
--no-build Don't build an image, even if it's missing
|
--no-build Don't build an image, even if it's missing.
|
||||||
|
--build Build images before creating containers.
|
||||||
```
|
```
|
||||||
|
|
||||||
Creates containers for a service.
|
|
||||||
|
@ -15,22 +15,24 @@ parent = "smn_compose_cli"
|
|||||||
Usage: up [options] [SERVICE...]
|
Usage: up [options] [SERVICE...]
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
-d Detached mode: Run containers in the background,
|
-d Detached mode: Run containers in the background,
|
||||||
print new container names.
|
print new container names.
|
||||||
Incompatible with --abort-on-container-exit.
|
Incompatible with --abort-on-container-exit.
|
||||||
--no-color Produce monochrome output.
|
--no-color Produce monochrome output.
|
||||||
--no-deps Don't start linked services.
|
--no-deps Don't start linked services.
|
||||||
--force-recreate Recreate containers even if their configuration
|
--force-recreate Recreate containers even if their configuration
|
||||||
and image haven't changed.
|
and image haven't changed.
|
||||||
Incompatible with --no-recreate.
|
Incompatible with --no-recreate.
|
||||||
--no-recreate If containers already exist, don't recreate them.
|
--no-recreate If containers already exist, don't recreate them.
|
||||||
Incompatible with --force-recreate.
|
Incompatible with --force-recreate.
|
||||||
--no-build Don't build an image, even if it's missing
|
--no-build Don't build an image, even if it's missing.
|
||||||
--abort-on-container-exit Stops all containers if any container was stopped.
|
--build Build images before starting containers.
|
||||||
Incompatible with -d.
|
--abort-on-container-exit Stops all containers if any container was stopped.
|
||||||
-t, --timeout TIMEOUT Use this timeout in seconds for container shutdown
|
Incompatible with -d.
|
||||||
when attached or when containers are already
|
-t, --timeout TIMEOUT Use this timeout in seconds for container shutdown
|
||||||
running. (default: 10)
|
when attached or when containers are already
|
||||||
|
running. (default: 10)
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Builds, (re)creates, starts, and attaches to containers for a service.
|
Builds, (re)creates, starts, and attaches to containers for a service.
|
||||||
|
@ -2,6 +2,7 @@ from __future__ import absolute_import
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import docker
|
import docker
|
||||||
|
import pytest
|
||||||
from docker.errors import APIError
|
from docker.errors import APIError
|
||||||
|
|
||||||
from .. import mock
|
from .. import mock
|
||||||
@ -15,6 +16,7 @@ from compose.const import LABEL_SERVICE
|
|||||||
from compose.container import Container
|
from compose.container import Container
|
||||||
from compose.service import build_ulimits
|
from compose.service import build_ulimits
|
||||||
from compose.service import build_volume_binding
|
from compose.service import build_volume_binding
|
||||||
|
from compose.service import BuildAction
|
||||||
from compose.service import ContainerNetworkMode
|
from compose.service import ContainerNetworkMode
|
||||||
from compose.service import get_container_data_volumes
|
from compose.service import get_container_data_volumes
|
||||||
from compose.service import ImageType
|
from compose.service import ImageType
|
||||||
@ -427,7 +429,12 @@ class ServiceTest(unittest.TestCase):
|
|||||||
'{"stream": "Successfully built abcd"}',
|
'{"stream": "Successfully built abcd"}',
|
||||||
]
|
]
|
||||||
|
|
||||||
service.create_container(do_build=True)
|
with mock.patch('compose.service.log', autospec=True) as mock_log:
|
||||||
|
service.create_container(do_build=BuildAction.none)
|
||||||
|
assert mock_log.warn.called
|
||||||
|
_, args, _ = mock_log.warn.mock_calls[0]
|
||||||
|
assert 'was built because it did not already exist' in args[0]
|
||||||
|
|
||||||
self.mock_client.build.assert_called_once_with(
|
self.mock_client.build.assert_called_once_with(
|
||||||
tag='default_foo',
|
tag='default_foo',
|
||||||
dockerfile=None,
|
dockerfile=None,
|
||||||
@ -444,14 +451,37 @@ class ServiceTest(unittest.TestCase):
|
|||||||
service = Service('foo', client=self.mock_client, build={'context': '.'})
|
service = Service('foo', client=self.mock_client, build={'context': '.'})
|
||||||
self.mock_client.inspect_image.return_value = {'Id': 'abc123'}
|
self.mock_client.inspect_image.return_value = {'Id': 'abc123'}
|
||||||
|
|
||||||
service.create_container(do_build=False)
|
service.create_container(do_build=BuildAction.skip)
|
||||||
self.assertFalse(self.mock_client.build.called)
|
self.assertFalse(self.mock_client.build.called)
|
||||||
|
|
||||||
def test_create_container_no_build_but_needs_build(self):
|
def test_create_container_no_build_but_needs_build(self):
|
||||||
service = Service('foo', client=self.mock_client, build={'context': '.'})
|
service = Service('foo', client=self.mock_client, build={'context': '.'})
|
||||||
self.mock_client.inspect_image.side_effect = NoSuchImageError
|
self.mock_client.inspect_image.side_effect = NoSuchImageError
|
||||||
with self.assertRaises(NeedsBuildError):
|
with pytest.raises(NeedsBuildError):
|
||||||
service.create_container(do_build=False)
|
service.create_container(do_build=BuildAction.skip)
|
||||||
|
|
||||||
|
def test_create_container_force_build(self):
|
||||||
|
service = Service('foo', client=self.mock_client, build={'context': '.'})
|
||||||
|
self.mock_client.inspect_image.return_value = {'Id': 'abc123'}
|
||||||
|
self.mock_client.build.return_value = [
|
||||||
|
'{"stream": "Successfully built abcd"}',
|
||||||
|
]
|
||||||
|
|
||||||
|
with mock.patch('compose.service.log', autospec=True) as mock_log:
|
||||||
|
service.create_container(do_build=BuildAction.force)
|
||||||
|
|
||||||
|
assert not mock_log.warn.called
|
||||||
|
self.mock_client.build.assert_called_once_with(
|
||||||
|
tag='default_foo',
|
||||||
|
dockerfile=None,
|
||||||
|
stream=True,
|
||||||
|
path='.',
|
||||||
|
pull=False,
|
||||||
|
forcerm=False,
|
||||||
|
nocache=False,
|
||||||
|
rm=True,
|
||||||
|
buildargs=None,
|
||||||
|
)
|
||||||
|
|
||||||
def test_build_does_not_pull(self):
|
def test_build_does_not_pull(self):
|
||||||
self.mock_client.build.return_value = [
|
self.mock_client.build.return_value = [
|
||||||
|
Loading…
x
Reference in New Issue
Block a user