Repository: getmoto/moto

Issue:
Breaking changes in docker
## Reporting Bugs

Hey, 
If there is no docker deamon running, then using `mock_lambda` with the newest version of docker-py (4.3.1) yields the following error:

```
docker.errors.DockerException: Error while fetching server API version: ('Connection aborted.', ConnectionRefusedError(61, 'Connection refused'))
```
Version 4.3.0 does not have this problem.



Discussion:
Thanks for raising this @saartochner. Looks like it's related to https://github.com/docker/docker-py/issues/2665 and https://github.com/docker/docker-py/issues/2676

Resolving patch:
diff --git a/moto/awslambda/models.py b/moto/awslambda/models.py
--- a/moto/awslambda/models.py
+++ b/moto/awslambda/models.py
@@ -17,13 +17,12 @@
 import re
 import zipfile
 import uuid
-import functools
 import tarfile
 import calendar
 import threading
 import traceback
 import weakref
-import requests.adapters
+import requests.exceptions
 
 from boto3 import Session
 
@@ -47,6 +46,7 @@
 from moto.dynamodb2 import dynamodb_backends2
 from moto.dynamodbstreams import dynamodbstreams_backends
 from moto.core import ACCOUNT_ID
+from moto.utilities.docker_utilities import DockerModel
 
 logger = logging.getLogger(__name__)
 
@@ -55,7 +55,6 @@
 except ImportError:
     from backports.tempfile import TemporaryDirectory
 
-_orig_adapter_send = requests.adapters.HTTPAdapter.send
 docker_3 = docker.__version__[0] >= "3"
 
 
@@ -151,8 +150,9 @@ def __exit__(self, exc_type, exc_val, exc_tb):
                     raise  # multiple processes trying to use same volume?
 
 
-class LambdaFunction(CloudFormationModel):
+class LambdaFunction(CloudFormationModel, DockerModel):
     def __init__(self, spec, region, validate_s3=True, version=1):
+        DockerModel.__init__(self)
         # required
         self.region = region
         self.code = spec["Code"]
@@ -162,25 +162,10 @@ def __init__(self, spec, region, validate_s3=True, version=1):
         self.run_time = spec["Runtime"]
         self.logs_backend = logs_backends[self.region]
         self.environment_vars = spec.get("Environment", {}).get("Variables", {})
-        self.docker_client = docker.from_env()
         self.policy = None
         self.state = "Active"
         self.reserved_concurrency = spec.get("ReservedConcurrentExecutions", None)
 
-        # Unfortunately mocking replaces this method w/o fallback enabled, so we
-        # need to replace it if we detect it's been mocked
-        if requests.adapters.HTTPAdapter.send != _orig_adapter_send:
-            _orig_get_adapter = self.docker_client.api.get_adapter
-
-            def replace_adapter_send(*args, **kwargs):
-                adapter = _orig_get_adapter(*args, **kwargs)
-
-                if isinstance(adapter, requests.adapters.HTTPAdapter):
-                    adapter.send = functools.partial(_orig_adapter_send, adapter)
-                return adapter
-
-            self.docker_client.api.get_adapter = replace_adapter_send
-
         # optional
         self.description = spec.get("Description", "")
         self.memory_size = spec.get("MemorySize", 128)
diff --git a/moto/batch/models.py b/moto/batch/models.py
--- a/moto/batch/models.py
+++ b/moto/batch/models.py
@@ -1,6 +1,5 @@
 from __future__ import unicode_literals
 import re
-import requests.adapters
 from itertools import cycle
 import six
 import datetime
@@ -8,7 +7,6 @@
 import uuid
 import logging
 import docker
-import functools
 import threading
 import dateutil.parser
 from boto3 import Session
@@ -30,8 +28,8 @@
 from moto.ec2.models import INSTANCE_TYPES as EC2_INSTANCE_TYPES
 from moto.iam.exceptions import IAMNotFoundException
 from moto.core import ACCOUNT_ID as DEFAULT_ACCOUNT_ID
+from moto.utilities.docker_utilities import DockerModel
 
-_orig_adapter_send = requests.adapters.HTTPAdapter.send
 logger = logging.getLogger(__name__)
 COMPUTE_ENVIRONMENT_NAME_REGEX = re.compile(
     r"^[A-Za-z0-9][A-Za-z0-9_-]{1,126}[A-Za-z0-9]$"
@@ -311,7 +309,7 @@ def create_from_cloudformation_json(
         return backend.get_job_definition_by_arn(arn)
 
 
-class Job(threading.Thread, BaseModel):
+class Job(threading.Thread, BaseModel, DockerModel):
     def __init__(self, name, job_def, job_queue, log_backend, container_overrides):
         """
         Docker Job
@@ -324,6 +322,7 @@ def __init__(self, name, job_def, job_queue, log_backend, container_overrides):
         :type log_backend: moto.logs.models.LogsBackend
         """
         threading.Thread.__init__(self)
+        DockerModel.__init__(self)
 
         self.job_name = name
         self.job_id = str(uuid.uuid4())
@@ -342,24 +341,9 @@ def __init__(self, name, job_def, job_queue, log_backend, container_overrides):
         self.daemon = True
         self.name = "MOTO-BATCH-" + self.job_id
 
-        self.docker_client = docker.from_env()
         self._log_backend = log_backend
         self.log_stream_name = None
 
-        # Unfortunately mocking replaces this method w/o fallback enabled, so we
-        # need to replace it if we detect it's been mocked
-        if requests.adapters.HTTPAdapter.send != _orig_adapter_send:
-            _orig_get_adapter = self.docker_client.api.get_adapter
-
-            def replace_adapter_send(*args, **kwargs):
-                adapter = _orig_get_adapter(*args, **kwargs)
-
-                if isinstance(adapter, requests.adapters.HTTPAdapter):
-                    adapter.send = functools.partial(_orig_adapter_send, adapter)
-                return adapter
-
-            self.docker_client.api.get_adapter = replace_adapter_send
-
     def describe(self):
         result = {
             "jobDefinition": self.job_definition.arn,
diff --git a/moto/utilities/docker_utilities.py b/moto/utilities/docker_utilities.py
new file mode 100644
--- /dev/null
+++ b/moto/utilities/docker_utilities.py
@@ -0,0 +1,33 @@
+import docker
+import functools
+import requests.adapters
+
+
+_orig_adapter_send = requests.adapters.HTTPAdapter.send
+
+
+class DockerModel:
+    def __init__(self):
+        self.__docker_client = None
+
+    @property
+    def docker_client(self):
+        if self.__docker_client is None:
+            # We should only initiate the Docker Client at runtime.
+            # The docker.from_env() call will fall if Docker is not running
+            self.__docker_client = docker.from_env()
+
+            # Unfortunately mocking replaces this method w/o fallback enabled, so we
+            # need to replace it if we detect it's been mocked
+            if requests.adapters.HTTPAdapter.send != _orig_adapter_send:
+                _orig_get_adapter = self.docker_client.api.get_adapter
+
+                def replace_adapter_send(*args, **kwargs):
+                    adapter = _orig_get_adapter(*args, **kwargs)
+
+                    if isinstance(adapter, requests.adapters.HTTPAdapter):
+                        adapter.send = functools.partial(_orig_adapter_send, adapter)
+                    return adapter
+
+                self.docker_client.api.get_adapter = replace_adapter_send
+        return self.__docker_client


Test patch: