Repository: PrefectHQ/prefect

Issue:
Expose TLS config on Docker storage
## Opened from the [Prefect Public Slack Community](https://join.slack.com/t/prefect-public/shared_invite/enQtNzE5OTU3OTQwNzc1LTQ5M2FkZmQzZjI0ODg1ZTBmOTc0ZjVjYWFjMWExZDAyYzBmYjVmMTE1NTQ1Y2IxZTllOTc4MmI3NzYxMDlhYWU)

**aseev.arsenii**: Hey all! I'm trying to set up a CircleCI job that builds and pushes flows Docker images to AWS ECR. CircleCI provides a remote docker execution environment, and to connect to it I need to pass on TLS configuration to the docker client. Unfortunately Prefect seems to only expose the `base_image` of `docker.APIClient` (<https://github.com/PrefectHQ/prefect/blob/1447982cc63a1f87c4a19cc7be18f2b2693fb883/src/prefect/environments/storage/docker.py#L508>). Any suggestions on how to do this? Thanks!

**laura**: Hi <@UR914ST7H>, I’m not sure of a workaround per se but it looks like the signature of exactly where you found the client instantiation could be changed to accept TLS config, configured by the user against an attribute of the `Docker` Storage instance like you can for `base_url` now. Let me get the bot to open an issue off this thread.

**laura**: <@ULVA73B9P> open “Expose TLS config on Docker storage”

Original thread can be found [here](https://prefect-community.slack.com/archives/CL09KU1K7/p1590055135302100?thread_ts=1590055135.302100&cid=CL09KU1K7).




Discussion:

Resolving patch:
diff --git a/src/prefect/environments/storage/docker.py b/src/prefect/environments/storage/docker.py
--- a/src/prefect/environments/storage/docker.py
+++ b/src/prefect/environments/storage/docker.py
@@ -9,7 +9,7 @@
 import uuid
 import warnings
 from pathlib import PurePosixPath
-from typing import TYPE_CHECKING, Any, Callable, Dict, Iterable, List
+from typing import TYPE_CHECKING, Any, Callable, Dict, Iterable, List, Union
 
 import cloudpickle
 import pendulum
@@ -56,8 +56,6 @@ class Docker(Storage):
             use when building
         - files (dict, optional): a dictionary of files to copy into the image
             when building
-        - base_url: (str, optional): a URL of a Docker daemon to use when for
-            Docker related functionality.  Defaults to DOCKER_HOST env var if not set
         - prefect_version (str, optional): an optional branch, tag, or commit
             specifying the version of prefect you want installed into the container;
             defaults to the version you are currently using or `"master"` if your
@@ -69,6 +67,10 @@ class Docker(Storage):
             are included.
         - secrets (List[str], optional): a list of Prefect Secrets which will be used to populate `prefect.context`
             for each flow run.  Used primarily for providing authentication credentials.
+        - base_url: (str, optional): a URL of a Docker daemon to use when for
+            Docker related functionality.  Defaults to DOCKER_HOST env var if not set
+        - tls_config: (Union[bool, docker.tls.TLSConfig], optional): a TLS configuration to pass to the Docker
+            client. https://docker-py.readthedocs.io/en/stable/tls.html#docker.tls.TLSConfig
 
     Raises:
         - ValueError: if both `base_image` and `dockerfile` are provided
@@ -85,11 +87,12 @@ def __init__(
         image_tag: str = None,
         env_vars: dict = None,
         files: dict = None,
-        base_url: str = None,
         prefect_version: str = None,
         local_image: bool = False,
         ignore_healthchecks: bool = False,
         secrets: List[str] = None,
+        base_url: str = None,
+        tls_config: Union[bool, "docker.tls.TLSConfig"] = False,
     ) -> None:
         self.registry_url = registry_url
         if sys.platform == "win32":
@@ -109,11 +112,13 @@ def __init__(
         self.files = files or {}
         self.flows = dict()  # type: Dict[str, str]
         self._flows = dict()  # type: Dict[str, "prefect.core.flow.Flow"]
-        self.base_url = base_url or os.environ.get("DOCKER_HOST", default_url)
         self.local_image = local_image
         self.extra_commands = []  # type: List[str]
         self.ignore_healthchecks = ignore_healthchecks
 
+        self.base_url = base_url or os.environ.get("DOCKER_HOST", default_url)
+        self.tls_config = tls_config
+
         version = prefect.__version__.split("+")
         if prefect_version is None:
             self.prefect_version = "master" if len(version) > 1 else version[0]
@@ -505,7 +510,9 @@ def _get_client(self) -> "docker.APIClient":
         # the 'import prefect' time low
         import docker
 
-        return docker.APIClient(base_url=self.base_url, version="auto")
+        return docker.APIClient(
+            base_url=self.base_url, version="auto", tls=self.tls_config
+        )
 
     def pull_image(self) -> None:
         """Pull the image specified so it can be built.


Test patch:
diff --git a/tests/environments/storage/test_docker_storage.py b/tests/environments/storage/test_docker_storage.py
--- a/tests/environments/storage/test_docker_storage.py
+++ b/tests/environments/storage/test_docker_storage.py
@@ -74,6 +74,7 @@ def test_empty_docker_storage(monkeypatch, platform, url, no_docker_host_var):
     assert not storage.files
     assert storage.prefect_version
     assert storage.base_url == url
+    assert storage.tls_config == False
     assert not storage.local_image
     assert not storage.ignore_healthchecks
 
@@ -138,6 +139,7 @@ def test_initialized_docker_storage(no_docker_host_var):
         image_tag="test5",
         env_vars={"test": "1"},
         base_url="test_url",
+        tls_config={"tls": "here"},
         prefect_version="my-branch",
         local_image=True,
     )
@@ -152,10 +154,26 @@ def test_initialized_docker_storage(no_docker_host_var):
         "PREFECT__USER_CONFIG_PATH": "/opt/prefect/config.toml",
     }
     assert storage.base_url == "test_url"
+    assert storage.tls_config == {"tls": "here"}
     assert storage.prefect_version == "my-branch"
     assert storage.local_image
 
 
+def test_initialized_docker_storage_client(monkeypatch, no_docker_host_var):
+    client = MagicMock()
+    monkeypatch.setattr("docker.APIClient", client)
+
+    storage = Docker(
+        registry_url="test1", base_url="test_url", tls_config={"tls": "here"},
+    )
+
+    storage._get_client()
+
+    assert client.call_args[1]["base_url"] == "test_url"
+    assert client.call_args[1]["tls"] == {"tls": "here"}
+    assert client.call_args[1]["version"] == "auto"
+
+
 def test_env_var_precedence_docker_storage(monkeypatch, no_docker_host_var):
     monkeypatch.setenv("DOCKER_HOST", "bar")
     storage = Docker()