Repository: PrefectHQ/prefect

Issue:
Flow storage copying fails with spaces in flow.name
```
Step 1/9 : FROM python:3.6
 ---> 2bb3204ab1d1
Step 2/9 : RUN pip install pip --upgrade
 ---> Using cache
 ---> 4d8b101e2933
Step 3/9 : RUN pip install wheel
 ---> Using cache
 ---> 7389d69ba240
Step 4/9 : RUN mkdir /root/.prefect/
 ---> Using cache
 ---> 10619fcde458
Step 5/9 : COPY Test Flow.flow /root/.prefect/TestFlow.prefect
Traceback (most recent call last):
  File "managed_agent_testing.py", line 15, in <module>
    f.deploy(project_name="Test Project")
  File "/Users/josh/Desktop/code/prefect/src/prefect/core/flow.py", line 1361, in deploy
    set_schedule_inactive=set_schedule_inactive,
  File "/Users/josh/Desktop/code/prefect/src/prefect/client/client.py", line 357, in deploy
    serializedFlow=flow.serialize(build=build),
  File "/Users/josh/Desktop/code/prefect/src/prefect/core/flow.py", line 1116, in serialize
    storage = self.storage.build()  # type: Optional[Storage]
  File "/Users/josh/Desktop/code/prefect/src/prefect/environments/storage/docker.py", line 170, in build
    image_name, image_tag = self.build_image(push=push)
  File "/Users/josh/Desktop/code/prefect/src/prefect/environments/storage/docker.py", line 219, in build_image
    "Your flow failed to deserialize in the container; please ensure that all necessary files and dependencies have been included."
prefect.utilities.exceptions.SerializationError: Your flow failed to deserialize in the container; please ensure that all necessary files and dependencies have been included.
```

Code to reproduce:
```
from prefect import task, Flow
from prefect.environments.storage import Docker


@task
def my_task():
    print("ASDF")


with Flow(
    "Test Flow", storage=Docker(...)
) as f:
    t1 = my_task()

f.deploy(project_name="Test Project")
```

`COPY Test Flow.flow /root/.prefect/TestFlow.prefect` the whitespace between Test and Flow.flow is the issue. We need to sanitize this by replacing it with a special character



Discussion:
Since we already require `slugify`, we could use that as a quick off-the-shelf solution:
```python
import slugify                  
                                                                                                                            
slugify.slugify("Test Flow")                                                                                                                                
## 'test-flow'
```
Check emojis too...

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
@@ -7,6 +7,7 @@
 import tempfile
 import textwrap
 import uuid
+from slugify import slugify
 from typing import Any, Callable, Dict, Iterable, List
 
 import docker
@@ -125,7 +126,7 @@ def add_flow(self, flow: "prefect.core.flow.Flow") -> str:
                     flow.name
                 )
             )
-        flow_path = "/root/.prefect/{}.prefect".format(flow.name.replace(" ", ""))
+        flow_path = "/root/.prefect/{}.prefect".format(slugify(flow.name))
         self.flows[flow.name] = flow_path
         self._flows[flow.name] = flow  # needed prior to build
         return flow_path
@@ -289,11 +290,12 @@ def create_dockerfile_object(self, directory: str = None) -> None:
             # Write all flows to file and load into the image
             copy_flows = ""
             for flow_name, flow_location in self.flows.items():
-                flow_path = os.path.join(directory, "{}.flow".format(flow_name))
+                clean_name = slugify(flow_name)
+                flow_path = os.path.join(directory, "{}.flow".format(clean_name))
                 with open(flow_path, "wb") as f:
                     cloudpickle.dump(self._flows[flow_name], f)
                 copy_flows += "COPY {source} {dest}\n".format(
-                    source="{}.flow".format(flow_name), dest=flow_location
+                    source="{}.flow".format(clean_name), dest=flow_location
                 )
 
             # Write a healthcheck script into the image


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
@@ -185,6 +185,27 @@ def test_create_dockerfile_from_base_image():
         assert "FROM python:3.6" in output
 
 
+def test_create_dockerfile_with_weird_flow_name():
+    with tempfile.TemporaryDirectory() as tempdir_outside:
+
+        with open(os.path.join(tempdir_outside, "test"), "w+") as t:
+            t.write("asdf")
+
+        with tempfile.TemporaryDirectory() as tempdir:
+
+            storage = Docker(registry_url="test1", base_image="test3")
+            f = Flow("WHAT IS THIS !!! ~~~~")
+            storage.add_flow(f)
+            storage.create_dockerfile_object(directory=tempdir)
+
+            with open(os.path.join(tempdir, "Dockerfile"), "r") as dockerfile:
+                output = dockerfile.read()
+
+            assert (
+                "COPY what-is-this.flow /root/.prefect/what-is-this.prefect" in output
+            )
+
+
 def test_create_dockerfile_from_everything():
 
     with tempfile.TemporaryDirectory() as tempdir_outside:
@@ -278,3 +299,13 @@ def test_add_similar_flows_fails():
     storage.add_flow(flow)
     with pytest.raises(ValueError):
         storage.add_flow(flow)
+
+
+def test_add_flow_with_weird_name_is_cleaned():
+    storage = Docker()
+    flow = prefect.Flow("WELL what do you know?!~? looks like a test!!!!")
+    loc = storage.add_flow(flow)
+    assert "?" not in loc
+    assert "!" not in loc
+    assert " " not in loc
+    assert "~" not in loc