Repository: aws/aws-sam-cli

Issue:
'Docker is unreachable' error message not shown in some scenarios
<!-- Make sure we don't have an existing Issue that reports the bug you are seeing (both open and closed). 
If you do find an existing Issue, re-open or add a comment to that Issue instead of creating a new one. -->

### Description

I tried running my local Lambda function locally this morning and was greeted with this cryptic error.
```
2019-08-30 16:23:41 Starting Build inside a container
2019-08-30 16:23:46 Building resource 'Function'
Traceback (most recent call last):
  File "runpy.py", line 193, in _run_module_as_main
  File "runpy.py", line 85, in _run_code
  File "C:\Program Files\Amazon\AWSSAMCLI\runtime\lib\site-packages\samcli\__main__.py", line 12, in <module>
    cli(prog_name="sam")
  File "C:\Program Files\Amazon\AWSSAMCLI\runtime\lib\site-packages\click\core.py", line 722, in __call__
    return self.main(*args, **kwargs)
  File "C:\Program Files\Amazon\AWSSAMCLI\runtime\lib\site-packages\click\core.py", line 697, in main
    rv = self.invoke(ctx)
  File "C:\Program Files\Amazon\AWSSAMCLI\runtime\lib\site-packages\click\core.py", line 1066, in invoke
    return _process_result(sub_ctx.command.invoke(sub_ctx))
  File "C:\Program Files\Amazon\AWSSAMCLI\runtime\lib\site-packages\click\core.py", line 895, in invoke
    return ctx.invoke(self.callback, **ctx.params)
  File "C:\Program Files\Amazon\AWSSAMCLI\runtime\lib\site-packages\click\core.py", line 535, in invoke
    return callback(*args, **kwargs)
  File "C:\Program Files\Amazon\AWSSAMCLI\runtime\lib\site-packages\click\decorators.py", line 64, in new_func
    return ctx.invoke(f, obj, *args[1:], **kwargs)
  File "C:\Program Files\Amazon\AWSSAMCLI\runtime\lib\site-packages\click\core.py", line 535, in invoke
    return callback(*args, **kwargs)
  File "C:\Program Files\Amazon\AWSSAMCLI\runtime\lib\site-packages\samcli\lib\telemetry\metrics.py", line 94, in wrapped
    raise exception  # pylint: disable=raising-bad-type
  File "C:\Program Files\Amazon\AWSSAMCLI\runtime\lib\site-packages\samcli\lib\telemetry\metrics.py", line 65, in wrapped
    return_value = func(*args, **kwargs)
  File "C:\Program Files\Amazon\AWSSAMCLI\runtime\lib\site-packages\samcli\commands\build\command.py", line 105, in cli
    skip_pull_image, parameter_overrides, mode)  # pragma: no cover
  File "C:\Program Files\Amazon\AWSSAMCLI\runtime\lib\site-packages\samcli\commands\build\command.py", line 150, in do_cli
    artifacts = builder.build()
  File "C:\Program Files\Amazon\AWSSAMCLI\runtime\lib\site-packages\samcli\lib\build\app_builder.py", line 108, in build
    lambda_function.runtime)
  File "C:\Program Files\Amazon\AWSSAMCLI\runtime\lib\site-packages\samcli\lib\build\app_builder.py", line 199, in _build_function
    runtime)
  File "C:\Program Files\Amazon\AWSSAMCLI\runtime\lib\site-packages\samcli\lib\build\app_builder.py", line 234, in _build_function_on_container
    if not self._container_manager.is_docker_reachable:
  File "C:\Program Files\Amazon\AWSSAMCLI\runtime\lib\site-packages\samcli\local\docker\manager.py", line 50, in is_docker_reachable
    self.docker_client.ping()
  File "C:\Program Files\Amazon\AWSSAMCLI\runtime\lib\site-packages\docker\client.py", line 187, in ping
    return self.api.ping(*args, **kwargs)
  File "C:\Program Files\Amazon\AWSSAMCLI\runtime\lib\site-packages\docker\api\daemon.py", line 166, in ping
    return self._result(self._get(self._url('/_ping'))) == 'OK'
  File "C:\Program Files\Amazon\AWSSAMCLI\runtime\lib\site-packages\docker\utils\decorators.py", line 46, in inner
    return f(self, *args, **kwargs)
  File "C:\Program Files\Amazon\AWSSAMCLI\runtime\lib\site-packages\docker\api\client.py", line 230, in _get
    return self.get(url, **self._set_request_timeout(kwargs))
  File "C:\Program Files\Amazon\AWSSAMCLI\runtime\lib\site-packages\requests\sessions.py", line 546, in get
    return self.request('GET', url, **kwargs)
  File "C:\Program Files\Amazon\AWSSAMCLI\runtime\lib\site-packages\requests\sessions.py", line 533, in request
    resp = self.send(prep, **send_kwargs)
  File "C:\Program Files\Amazon\AWSSAMCLI\runtime\lib\site-packages\requests\sessions.py", line 646, in send
    r = adapter.send(request, **kwargs)
  File "C:\Program Files\Amazon\AWSSAMCLI\runtime\lib\site-packages\requests\adapters.py", line 449, in send
    timeout=timeout
  File "C:\Program Files\Amazon\AWSSAMCLI\runtime\lib\site-packages\urllib3\connectionpool.py", line 603, in urlopen
    chunked=chunked)
  File "C:\Program Files\Amazon\AWSSAMCLI\runtime\lib\site-packages\urllib3\connectionpool.py", line 355, in _make_request
    conn.request(method, url, **httplib_request_kw)
  File "http\client.py", line 1239, in request
  File "http\client.py", line 1285, in _send_request
  File "http\client.py", line 1234, in endheaders
  File "http\client.py", line 1026, in _send_output
  File "http\client.py", line 964, in send
  File "C:\Program Files\Amazon\AWSSAMCLI\runtime\lib\site-packages\docker\transport\npipeconn.py", line 32, in connect
    sock.connect(self.npipe_path)
  File "C:\Program Files\Amazon\AWSSAMCLI\runtime\lib\site-packages\docker\transport\npipesocket.py", line 22, in wrapped
    return f(self, *args, **kwargs)
  File "C:\Program Files\Amazon\AWSSAMCLI\runtime\lib\site-packages\docker\transport\npipesocket.py", line 50, in connect
    win32pipe.WaitNamedPipe(address, self._timeout)
pywintypes.error: (2, 'WaitNamedPipe', 'The system cannot find the file specified.')
```
This turned out to be because Docker Desktop was not running but I was adding the --use-container argument.

I started Docker Desktop, and continually tried running the function. Interestingly, **only if Docker is in the process of starting**, do you get an informative error message:
```
2019-08-30 16:26:39 Starting Build inside a container
2019-08-30 16:26:39 Building resource 'Function'

Build Failed
Error: Docker is unreachable. Docker needs to be running to build inside a container.
```
### Steps to reproduce

1. Start a local Lambda function with Docker Desktop closed and observe the ugly error
2. Start Docker Desktop and repeatedly run the local lambda function
3. After a while you will see the informative error.

### Observed result
```
2019-08-31 18:05:16 Changing event name from creating-client-class.iot-data to creating-client-class.iot-data-plane
2019-08-31 18:05:16 Changing event name from before-call.apigateway to before-call.api-gateway
2019-08-31 18:05:16 Changing event name from request-created.machinelearning.Predict to request-created.machine-learning.Predict
2019-08-31 18:05:16 Changing event name from before-parameter-build.autoscaling.CreateLaunchConfiguration to before-parameter-build.auto-scaling.CreateLaunchConfiguration
2019-08-31 18:05:16 Changing event name from before-parameter-build.route53 to before-parameter-build.route-53
2019-08-31 18:05:16 Changing event name from request-created.cloudsearchdomain.Search to request-created.cloudsearch-domain.Search
2019-08-31 18:05:16 Changing event name from docs.*.autoscaling.CreateLaunchConfiguration.complete-section to docs.*.auto-scaling.CreateLaunchConfiguration.complete-section
2019-08-31 18:05:16 Changing event name from before-parameter-build.logs.CreateExportTask to before-parameter-build.cloudwatch-logs.CreateExportTask
2019-08-31 18:05:16 Changing event name from docs.*.logs.CreateExportTask.complete-section to docs.*.cloudwatch-logs.CreateExportTask.complete-section
2019-08-31 18:05:16 Changing event name from before-parameter-build.cloudsearchdomain.Search to before-parameter-build.cloudsearch-domain.Search
2019-08-31 18:05:16 Changing event name from docs.*.cloudsearchdomain.Search.complete-section to docs.*.cloudsearch-domain.Search.complete-section
2019-08-31 18:05:16 Changing event name from creating-client-class.iot-data to creating-client-class.iot-data-plane
2019-08-31 18:05:16 Changing event name from before-call.apigateway to before-call.api-gateway
2019-08-31 18:05:16 Changing event name from request-created.machinelearning.Predict to request-created.machine-learning.Predict
2019-08-31 18:05:16 Changing event name from before-parameter-build.autoscaling.CreateLaunchConfiguration to before-parameter-build.auto-scaling.CreateLaunchConfiguration
2019-08-31 18:05:16 Changing event name from before-parameter-build.route53 to before-parameter-build.route-53
2019-08-31 18:05:16 Changing event name from request-created.cloudsearchdomain.Search to request-created.cloudsearch-domain.Search
2019-08-31 18:05:16 Changing event name from docs.*.autoscaling.CreateLaunchConfiguration.complete-section to docs.*.auto-scaling.CreateLaunchConfiguration.complete-section
2019-08-31 18:05:16 Changing event name from before-parameter-build.logs.CreateExportTask to before-parameter-build.cloudwatch-logs.CreateExportTask
2019-08-31 18:05:16 Changing event name from docs.*.logs.CreateExportTask.complete-section to docs.*.cloudwatch-logs.CreateExportTask.complete-section
2019-08-31 18:05:16 Changing event name from before-parameter-build.cloudsearchdomain.Search to before-parameter-build.cloudsearch-domain.Search
2019-08-31 18:05:16 Changing event name from docs.*.cloudsearchdomain.Search.complete-section to docs.*.cloudsearch-domain.Search.complete-section
2019-08-31 18:05:16 Telemetry endpoint configured to be https://aws-serverless-tools-telemetry.us-west-2.amazonaws.com/metrics
2019-08-31 18:05:16 'build' command is called
2019-08-31 18:05:16 Starting Build inside a container
2019-08-31 18:05:16 No Parameters detected in the template
2019-08-31 18:05:16 2 resources found in the template
2019-08-31 18:05:16 Found Serverless function with name='AutomatedSageMakerTraining' and CodeUri='.'
2019-08-31 18:05:16 Found Serverless function with name='AutomatedSageMakerEvaluation' and CodeUri='.'
2019-08-31 18:05:16 Trying paths: ['C:\\Users\\Oscar\\.docker\\config.json', 'C:\\Users\\Oscar\\.dockercfg']
2019-08-31 18:05:16 Found file at path: C:\Users\Oscar\.docker\config.json
2019-08-31 18:05:16 Found 'auths' section
2019-08-31 18:05:16 Auth data for https://index.docker.io/v1/ is absent. Client might be using a credentials store instead.
2019-08-31 18:05:16 Found 'credsStore' section
2019-08-31 18:05:16 Building resource 'AutomatedSageMakerTraining'
2019-08-31 18:05:16 Sending Telemetry: {'metrics': [{'commandRun': {'awsProfileProvided': False, 'debugFlagProvided': True, 'region': '', 'commandName': 'sam build', 'duration': 39, 'exitReason': 'error', 'exitCode': 255, 'requestId': 'f2b3683b-1131-49a7-b189-22bb5b5c408d', 'installationId': '0c5d4463-668a-4706-ae40-afd0704d66bd', 'sessionId': '2d3237ae-c0db-490b-8fc8-4f5f60b5d2de', 'executionEnvironment': 'CLI', 'pyversion': '3.6.7', 'samcliVersion': '0.19.0'}}]}
2019-08-31 18:05:16 Starting new HTTPS connection (1): aws-serverless-tools-telemetry.us-west-2.amazonaws.com:443
2019-08-31 18:05:17 HTTPSConnectionPool(host='aws-serverless-tools-telemetry.us-west-2.amazonaws.com', port=443): Read timed out. (read timeout=0.1)
Traceback (most recent call last):
  File "runpy.py", line 193, in _run_module_as_main
  File "runpy.py", line 85, in _run_code
  File "C:\Program Files\Amazon\AWSSAMCLI\runtime\lib\site-packages\samcli\__main__.py", line 12, in <module>
    cli(prog_name="sam")
  File "C:\Program Files\Amazon\AWSSAMCLI\runtime\lib\site-packages\click\core.py", line 722, in __call__
    return self.main(*args, **kwargs)
  File "C:\Program Files\Amazon\AWSSAMCLI\runtime\lib\site-packages\click\core.py", line 697, in main
    rv = self.invoke(ctx)
  File "C:\Program Files\Amazon\AWSSAMCLI\runtime\lib\site-packages\click\core.py", line 1066, in invoke
    return _process_result(sub_ctx.command.invoke(sub_ctx))
  File "C:\Program Files\Amazon\AWSSAMCLI\runtime\lib\site-packages\click\core.py", line 895, in invoke
    return ctx.invoke(self.callback, **ctx.params)
  File "C:\Program Files\Amazon\AWSSAMCLI\runtime\lib\site-packages\click\core.py", line 535, in invoke
    return callback(*args, **kwargs)
  File "C:\Program Files\Amazon\AWSSAMCLI\runtime\lib\site-packages\click\decorators.py", line 64, in new_func
    return ctx.invoke(f, obj, *args[1:], **kwargs)
  File "C:\Program Files\Amazon\AWSSAMCLI\runtime\lib\site-packages\click\core.py", line 535, in invoke
    return callback(*args, **kwargs)
  File "C:\Program Files\Amazon\AWSSAMCLI\runtime\lib\site-packages\samcli\lib\telemetry\metrics.py", line 94, in wrapped
    raise exception  # pylint: disable=raising-bad-type
  File "C:\Program Files\Amazon\AWSSAMCLI\runtime\lib\site-packages\samcli\lib\telemetry\metrics.py", line 65, in wrapped
    return_value = func(*args, **kwargs)
  File "C:\Program Files\Amazon\AWSSAMCLI\runtime\lib\site-packages\samcli\commands\build\command.py", line 105, in cli
    skip_pull_image, parameter_overrides, mode)  # pragma: no cover
  File "C:\Program Files\Amazon\AWSSAMCLI\runtime\lib\site-packages\samcli\commands\build\command.py", line 150, in do_cli
    artifacts = builder.build()
  File "C:\Program Files\Amazon\AWSSAMCLI\runtime\lib\site-packages\samcli\lib\build\app_builder.py", line 108, in build
    lambda_function.runtime)
  File "C:\Program Files\Amazon\AWSSAMCLI\runtime\lib\site-packages\samcli\lib\build\app_builder.py", line 199, in _build_function
    runtime)
  File "C:\Program Files\Amazon\AWSSAMCLI\runtime\lib\site-packages\samcli\lib\build\app_builder.py", line 234, in _build_function_on_container
    if not self._container_manager.is_docker_reachable:
  File "C:\Program Files\Amazon\AWSSAMCLI\runtime\lib\site-packages\samcli\local\docker\manager.py", line 50, in is_docker_reachable
    self.docker_client.ping()
  File "C:\Program Files\Amazon\AWSSAMCLI\runtime\lib\site-packages\docker\client.py", line 187, in ping
    return self.api.ping(*args, **kwargs)
  File "C:\Program Files\Amazon\AWSSAMCLI\runtime\lib\site-packages\docker\api\daemon.py", line 166, in ping
    return self._result(self._get(self._url('/_ping'))) == 'OK'
  File "C:\Program Files\Amazon\AWSSAMCLI\runtime\lib\site-packages\docker\utils\decorators.py", line 46, in inner
    return f(self, *args, **kwargs)
  File "C:\Program Files\Amazon\AWSSAMCLI\runtime\lib\site-packages\docker\api\client.py", line 230, in _get
    return self.get(url, **self._set_request_timeout(kwargs))
  File "C:\Program Files\Amazon\AWSSAMCLI\runtime\lib\site-packages\requests\sessions.py", line 546, in get
    return self.request('GET', url, **kwargs)
  File "C:\Program Files\Amazon\AWSSAMCLI\runtime\lib\site-packages\requests\sessions.py", line 533, in request
    resp = self.send(prep, **send_kwargs)
  File "C:\Program Files\Amazon\AWSSAMCLI\runtime\lib\site-packages\requests\sessions.py", line 646, in send
    r = adapter.send(request, **kwargs)
  File "C:\Program Files\Amazon\AWSSAMCLI\runtime\lib\site-packages\requests\adapters.py", line 449, in send
    timeout=timeout
  File "C:\Program Files\Amazon\AWSSAMCLI\runtime\lib\site-packages\urllib3\connectionpool.py", line 603, in urlopen
    chunked=chunked)
  File "C:\Program Files\Amazon\AWSSAMCLI\runtime\lib\site-packages\urllib3\connectionpool.py", line 355, in _make_request
    conn.request(method, url, **httplib_request_kw)
  File "http\client.py", line 1239, in request
  File "http\client.py", line 1285, in _send_request
  File "http\client.py", line 1234, in endheaders
  File "http\client.py", line 1026, in _send_output
  File "http\client.py", line 964, in send
  File "C:\Program Files\Amazon\AWSSAMCLI\runtime\lib\site-packages\docker\transport\npipeconn.py", line 32, in connect
    sock.connect(self.npipe_path)
  File "C:\Program Files\Amazon\AWSSAMCLI\runtime\lib\site-packages\docker\transport\npipesocket.py", line 22, in wrapped
    return f(self, *args, **kwargs)
  File "C:\Program Files\Amazon\AWSSAMCLI\runtime\lib\site-packages\docker\transport\npipesocket.py", line 50, in connect
    win32pipe.WaitNamedPipe(address, self._timeout)
pywintypes.error: (2, 'WaitNamedPipe', 'The system cannot find the file specified.')
```

### Expected result

The informative 'Docker is unreachable' error should be shown even if Docker Desktop is closed.

### Additional environment details (Ex: Windows, Mac, Amazon Linux etc)

1. OS: Windows 10 Education
2. `sam --version`: 0.19.0



Discussion:
Thanks for the issue! definitely a good catch, we should be surfacing an appropriate error even in the cases that docker is not running. Would you be interested in submitting a PR for this? :)
I've already made some changes that fixed it a few days ago, although I tried to follow your steps in `DEVELOPMENT_GUIDE.md`, and as I'm based in Windows this wasn't possible. I looked into `pipenv` but this just gives an error whenever I try to switch types. I spent about 15 minutes fixing the bug and 4 hours trying to set up the environment you want for contributing, and ended up giving up.

I'll see if I can get on a unix machine and try again soon.
As I tried and could not reproduce the issue in Ubuntu and in MacOS, I think the problem is Windows-specific. 
As @OscarVanL said above, `DEVELOPMENT_GUIDE.md` is quite unfriendly to developers on Windows. We could have a friendlier development guide for Windows users, and also need to address some Windows-specific errors. 
This issue didn't get any update for a while... I will pick up this issue and submit a PR soon.

Resolving patch:
diff --git a/samcli/commands/local/cli_common/invoke_context.py b/samcli/commands/local/cli_common/invoke_context.py
--- a/samcli/commands/local/cli_common/invoke_context.py
+++ b/samcli/commands/local/cli_common/invoke_context.py
@@ -134,7 +134,9 @@ def __enter__(self):
         self._container_manager = self._get_container_manager(self._docker_network, self._skip_pull_image)
 
         if not self._container_manager.is_docker_reachable:
-            raise InvokeContextException("Running AWS SAM projects locally requires Docker. Have you got it installed?")
+            raise InvokeContextException(
+                "Running AWS SAM projects locally requires Docker. Have you got it installed and running?"
+            )
 
         return self
 
diff --git a/samcli/local/docker/manager.py b/samcli/local/docker/manager.py
--- a/samcli/local/docker/manager.py
+++ b/samcli/local/docker/manager.py
@@ -6,6 +6,7 @@
 
 import sys
 import re
+import platform
 import docker
 import requests
 
@@ -44,13 +45,22 @@ def is_docker_reachable(self):
         bool
             True, if Docker is available, False otherwise
         """
+        errors = (
+            docker.errors.APIError,
+            requests.exceptions.ConnectionError,
+        )
+        if platform.system() == "Windows":
+            import pywintypes  # pylint: disable=import-error
+
+            errors += (pywintypes.error,)  # pylint: disable=no-member
+
         try:
             self.docker_client.ping()
-
             return True
 
         # When Docker is not installed, a request.exceptions.ConnectionError is thrown.
-        except (docker.errors.APIError, requests.exceptions.ConnectionError):
+        # and also windows-specific errors
+        except errors:
             LOG.debug("Docker is not reachable", exc_info=True)
             return False
 


Test patch:
diff --git a/tests/unit/commands/local/cli_common/test_invoke_context.py b/tests/unit/commands/local/cli_common/test_invoke_context.py
--- a/tests/unit/commands/local/cli_common/test_invoke_context.py
+++ b/tests/unit/commands/local/cli_common/test_invoke_context.py
@@ -127,7 +127,7 @@ def test_must_raise_if_docker_is_not_reachable(self, SamFunctionProviderMock):
                 invoke_context.__enter__()
 
                 self.assertEqual(
-                    "Running AWS SAM projects locally requires Docker. Have you got it installed?",
+                    "Running AWS SAM projects locally requires Docker. Have you got it installed and running?",
                     str(ex_ctx.exception),
                 )
 
diff --git a/tests/unit/local/docker/test_manager.py b/tests/unit/local/docker/test_manager.py
--- a/tests/unit/local/docker/test_manager.py
+++ b/tests/unit/local/docker/test_manager.py
@@ -3,11 +3,11 @@
 """
 
 import io
+import importlib
 from unittest import TestCase
+from unittest.mock import Mock, patch
 
 import requests
-
-from unittest.mock import Mock
 from docker.errors import APIError, ImageNotFound
 from samcli.local.docker.manager import ContainerManager, DockerImagePullFailedException
 
@@ -226,10 +226,10 @@ class TestContainerManager_is_docker_reachable(TestCase):
     def setUp(self):
         self.ping_mock = Mock()
 
-        docker_client_mock = Mock()
-        docker_client_mock.ping = self.ping_mock
+        self.docker_client_mock = Mock()
+        self.docker_client_mock.ping = self.ping_mock
 
-        self.manager = ContainerManager(docker_client=docker_client_mock)
+        self.manager = ContainerManager(docker_client=self.docker_client_mock)
 
     def test_must_use_docker_client_ping(self):
         self.manager.is_docker_reachable
@@ -255,6 +255,35 @@ def test_must_return_false_if_ping_raises_connection_error(self):
 
         self.assertFalse(is_reachable)
 
+    def test_must_return_false_if_ping_raises_pywintypes_error(self):
+        # pywintypes is not available non-Windows OS,
+        # we need to make up an Exception for this
+        class MockPywintypesError(Exception):
+            pass
+
+        # Mock these modules to simulate a Windows environment
+        platform_mock = Mock()
+        platform_mock.system.return_value = "Windows"
+        pywintypes_mock = Mock()
+        pywintypes_mock.error = MockPywintypesError
+        modules = {
+            "platform": platform_mock,
+            "pywintypes": pywintypes_mock,
+        }
+        with patch.dict("sys.modules", modules):
+            import samcli.local.docker.manager as manager_module
+
+            importlib.reload(manager_module)
+            manager = manager_module.ContainerManager(docker_client=self.docker_client_mock)
+            import pywintypes  # pylint: disable=import-error
+
+            self.ping_mock.side_effect = pywintypes.error("pywintypes.error")
+            is_reachable = manager.is_docker_reachable
+            self.assertFalse(is_reachable)
+
+        # reload modules to ensure platform.system() is unpatched
+        importlib.reload(manager_module)
+
 
 class TestContainerManager_has_image(TestCase):
     def setUp(self):