From c9083e21c81576ba7b8f27dfd952f269cc25a7fd Mon Sep 17 00:00:00 2001
From: Vojta Orgon <villlem@gmail.com>
Date: Mon, 21 Sep 2015 11:59:23 +0200
Subject: [PATCH] Flag to skip all pull errors when pulling images.

Signed-off-by: Vojta Orgon <villlem@gmail.com>
---
 compose/cli/main.py                                   |  2 ++
 compose/project.py                                    |  4 ++--
 compose/service.py                                    | 11 +++++++++--
 contrib/completion/bash/docker-compose                |  2 +-
 contrib/completion/zsh/_docker-compose                |  1 +
 docs/reference/pull.md                                |  3 +++
 .../simple-composefile/ignore-pull-failures.yml       |  6 ++++++
 tests/integration/cli_test.py                         |  7 +++++++
 8 files changed, 31 insertions(+), 5 deletions(-)
 create mode 100644 tests/fixtures/simple-composefile/ignore-pull-failures.yml

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'