diff --git a/compose/cli/main.py b/compose/cli/main.py index 9b03ea676..f32b2a529 100644 --- a/compose/cli/main.py +++ b/compose/cli/main.py @@ -270,6 +270,7 @@ class TopLevelCommand(Command): Usage: pull [options] [SERVICE...] Options: + --ignore-pull-failures Pull what it can and ignores images with pull failures. --allow-insecure-ssl Deprecated - no effect. """ if options['--allow-insecure-ssl']: @@ -277,6 +278,7 @@ class TopLevelCommand(Command): project.pull( service_names=options['SERVICE'], + ignore_pull_failures=options.get('--ignore-pull-failures') ) def rm(self, project, options): diff --git a/compose/project.py b/compose/project.py index f34cc0c34..4750a7a9a 100644 --- a/compose/project.py +++ b/compose/project.py @@ -311,9 +311,9 @@ class Project(object): return plans - def pull(self, service_names=None): + def pull(self, service_names=None, ignore_pull_failures=False): for service in self.get_services(service_names, include_deps=True): - service.pull() + service.pull(ignore_pull_failures) def containers(self, service_names=None, stopped=False, one_off=False): if service_names: diff --git a/compose/service.py b/compose/service.py index cf3b62709..960d3936b 100644 --- a/compose/service.py +++ b/compose/service.py @@ -769,7 +769,7 @@ class Service(object): return True return False - def pull(self): + def pull(self, ignore_pull_failures=False): if 'image' not in self.options: return @@ -781,7 +781,14 @@ class Service(object): tag=tag, stream=True, ) - stream_output(output, sys.stdout) + + try: + stream_output(output, sys.stdout) + except StreamOutputError as e: + if not ignore_pull_failures: + raise + else: + log.error(six.text_type(e)) class Net(object): diff --git a/contrib/completion/bash/docker-compose b/contrib/completion/bash/docker-compose index 28d94394c..ff09205cb 100644 --- a/contrib/completion/bash/docker-compose +++ b/contrib/completion/bash/docker-compose @@ -212,7 +212,7 @@ _docker_compose_ps() { _docker_compose_pull() { case "$cur" in -*) - COMPREPLY=( $( compgen -W "--help" -- "$cur" ) ) + COMPREPLY=( $( compgen -W "--help --ignore-pull-failures" -- "$cur" ) ) ;; *) __docker_compose_services_from_image diff --git a/contrib/completion/zsh/_docker-compose b/contrib/completion/zsh/_docker-compose index 58105dc22..99cb4dc57 100644 --- a/contrib/completion/zsh/_docker-compose +++ b/contrib/completion/zsh/_docker-compose @@ -237,6 +237,7 @@ __docker-compose_subcommand() { (pull) _arguments \ $opts_help \ + '--ignore-pull-failures[Pull what it can and ignores images with pull failures.]' \ '*:services:__docker-compose_services_from_image' && ret=0 ;; (rm) diff --git a/docs/reference/pull.md b/docs/reference/pull.md index d655dd93b..5ec184b72 100644 --- a/docs/reference/pull.md +++ b/docs/reference/pull.md @@ -13,6 +13,9 @@ parent = "smn_compose_cli" ``` Usage: pull [options] [SERVICE...] + +Options: +--ignore-pull-failures Pull what it can and ignores images with pull failures. ``` Pulls service images. diff --git a/tests/fixtures/simple-composefile/ignore-pull-failures.yml b/tests/fixtures/simple-composefile/ignore-pull-failures.yml new file mode 100644 index 000000000..a28f79223 --- /dev/null +++ b/tests/fixtures/simple-composefile/ignore-pull-failures.yml @@ -0,0 +1,6 @@ +simple: + image: busybox:latest + command: top +another: + image: nonexisting-image:latest + command: top diff --git a/tests/integration/cli_test.py b/tests/integration/cli_test.py index 9dadd0368..56e65a6dd 100644 --- a/tests/integration/cli_test.py +++ b/tests/integration/cli_test.py @@ -97,6 +97,13 @@ class CLITestCase(DockerClientTestCase): 'Pulling digest (busybox@' 'sha256:38a203e1986cf79639cfb9b2e1d6e773de84002feea2d4eb006b52004ee8502d)...') + @mock.patch('compose.service.log') + def test_pull_with_ignore_pull_failures(self, mock_logging): + self.command.dispatch(['-f', 'ignore-pull-failures.yml', 'pull', '--ignore-pull-failures'], None) + mock_logging.info.assert_any_call('Pulling simple (busybox:latest)...') + mock_logging.info.assert_any_call('Pulling another (nonexisting-image:latest)...') + mock_logging.error.assert_any_call('Error: image library/nonexisting-image:latest not found') + @mock.patch('sys.stdout', new_callable=StringIO) def test_build_plain(self, mock_stdout): self.command.base_dir = 'tests/fixtures/simple-dockerfile'