Repository: PrefectHQ/prefect

Issue:
Iron out steps for resolving serializer changes with Core's server
I am currently working on #2369 and in turn adding a new serializer effectively breaks its use with Core's server due to the version of prefect that the server has being behind (using the `latest` build).

```bash
In [14]: f = Flow('a', environment=RemoteDaskEnvironment(address="yes"))

In [15]: f.register()
Result Handler check: OK
---------------------------------------------------------------------------
ClientError                               Traceback (most recent call last)
<ipython-input-15-11702a1d3250> in <module>
----> 1 f.register()

~/Desktop/code/prefect/src/prefect/core/flow.py in register(self, project_name, build, labels, set_schedule_active, version_group_id, no_url, **kwargs)
   1371             set_schedule_active=set_schedule_active,
   1372             version_group_id=version_group_id,
-> 1373             no_url=no_url,
   1374         )
   1375         return registered_flow

~/Desktop/code/prefect/src/prefect/client/client.py in register(self, flow, project_name, build, set_schedule_active, version_group_id, compressed, no_url)
    624                     serialized_flow=serialized_flow,
    625                     set_schedule_active=set_schedule_active,
--> 626                     version_group_id=version_group_id,
    627                 )
    628             ),

~/Desktop/code/prefect/src/prefect/client/client.py in graphql(self, query, raise_on_error, headers, variables, token)
    223             elif "Malformed Authorization header" in str(result["errors"]):
    224                 raise AuthorizationError(result["errors"])
--> 225             raise ClientError(result["errors"])
    226         else:
    227             return GraphQLResult(result)  # type: ignore

ClientError: [{'message': "Invalid flow: {'environment': {'type': ['Unsupported value: RemoteDaskEnvironment']}}", 'locations': [{'line': 2, 'column': 5}], 'path': ['create_flow_from_compressed_string'], 'extensions': {'code': 'INTERNAL_SERVER_ERROR', 'exception': {'errors': [{'message': "Invalid flow: {'environment': {'type': ['Unsupported value: RemoteDaskEnvironment']}}", 'locations': [], 'path': ['create_flow_from_compressed_string']}]}}}]
```

This means that once #2369 (or any future serializers) are merged then when running the Core server images you would need to specify a `--version master` in order for it to work. I'm opening this issue as a discussion point on if this is the functionality we want or if there is a better way.


Discussion:

Resolving patch:
diff --git a/src/prefect/cli/server.py b/src/prefect/cli/server.py
--- a/src/prefect/cli/server.py
+++ b/src/prefect/cli/server.py
@@ -8,6 +8,7 @@
 import click
 import yaml
 
+import prefect
 from prefect import config
 from prefect.utilities.configuration import set_temporary_config
 from prefect.utilities.docker_util import platform_is_linux, get_docker_ip
@@ -90,8 +91,6 @@ def server():
     "--version",
     "-v",
     help="The server image versions to use (for example, '0.10.0' or 'master')",
-    # TODO: update this default to use prefect.__version__ logic
-    default="latest",
     hidden=True,
 )
 @click.option(
@@ -198,7 +197,7 @@ def start(
     \b
     Options:
         --version, -v   TEXT    The server image versions to use (for example, '0.10.0' or 'master')
-                                Defaults to 'latest'
+                                Defaults to the current installed Prefect version.
         --skip-pull             Flag to skip pulling new images (if available)
         --no-upgrade, -n        Flag to avoid running a database upgrade when the database spins up
         --no-ui, -u             Flag to avoid starting the UI
@@ -277,7 +276,11 @@ def start(
         env = make_env()
 
     if "PREFECT_SERVER_TAG" not in env:
-        env.update(PREFECT_SERVER_TAG=version)
+        env.update(
+            PREFECT_SERVER_TAG=version or "master"
+            if len(prefect.__version__.split("+")) > 1
+            else prefect.__version__
+        )
     if "PREFECT_SERVER_DB_CMD" not in env:
         cmd = (
             "prefect-server database upgrade -y"
diff --git a/src/prefect/environments/execution/dask/remote.py b/src/prefect/environments/execution/dask/remote.py
--- a/src/prefect/environments/execution/dask/remote.py
+++ b/src/prefect/environments/execution/dask/remote.py
@@ -51,10 +51,13 @@ def __init__(
         on_start: Callable = None,
         on_exit: Callable = None,
     ) -> None:
+        self.address = address
         dask_executor_kwargs = executor_kwargs or dict()
         dask_executor_kwargs["address"] = address
+
         if security:
             dask_executor_kwargs["security"] = security
+
         super().__init__(
             executor="prefect.engine.executors.DaskExecutor",
             executor_kwargs=dask_executor_kwargs,
diff --git a/src/prefect/serialization/environment.py b/src/prefect/serialization/environment.py
--- a/src/prefect/serialization/environment.py
+++ b/src/prefect/serialization/environment.py
@@ -9,6 +9,7 @@
     KubernetesJobEnvironment,
     LocalEnvironment,
     RemoteEnvironment,
+    RemoteDaskEnvironment,
 )
 from prefect.utilities.serialization import ObjectSchema, OneOfSchema, to_qualified_name
 
@@ -61,6 +62,14 @@ class Meta:
     labels = fields.List(fields.String())
 
 
+class RemoteDaskEnvironmentSchema(ObjectSchema):
+    class Meta:
+        object_class = RemoteDaskEnvironment
+
+    address = fields.String()
+    labels = fields.List(fields.String())
+
+
 class CustomEnvironmentSchema(ObjectSchema):
     class Meta:
         object_class = lambda: Environment
@@ -94,6 +103,7 @@ class EnvironmentSchema(OneOfSchema):
         "LocalEnvironment": LocalEnvironmentSchema,
         "KubernetesJobEnvironment": KubernetesJobEnvironmentSchema,
         "RemoteEnvironment": RemoteEnvironmentSchema,
+        "RemoteDaskEnvironment": RemoteDaskEnvironmentSchema,
         "CustomEnvironment": CustomEnvironmentSchema,
     }
 


Test patch:
diff --git a/tests/cli/test_server.py b/tests/cli/test_server.py
--- a/tests/cli/test_server.py
+++ b/tests/cli/test_server.py
@@ -1,7 +1,9 @@
 from unittest.mock import MagicMock
 
 from click.testing import CliRunner
+import pytest
 
+import prefect
 from prefect.cli.server import server, make_env
 from prefect.utilities.configuration import set_temporary_config
 
@@ -88,6 +90,37 @@ def test_server_start(monkeypatch, macos_platform):
     assert check_output.call_args[1].get("env")
 
 
+@pytest.mark.parametrize(
+    "version",
+    [
+        ("0.10.3", "0.10.3"),
+        ("0.10.3+114.g35bc7ba4", "master"),
+        ("0.10.2+999.gr34343.dirty", "master"),
+    ],
+)
+def test_server_start_image_versions(monkeypatch, version, macos_platform):
+    check_call = MagicMock()
+    popen = MagicMock(side_effect=KeyboardInterrupt())
+    check_output = MagicMock()
+    monkeypatch.setattr("subprocess.Popen", popen)
+    monkeypatch.setattr("subprocess.check_call", check_call)
+    monkeypatch.setattr("subprocess.check_output", check_output)
+    monkeypatch.setattr(prefect, "__version__", version[0])
+
+    runner = CliRunner()
+    result = runner.invoke(server, ["start"])
+    assert result.exit_code == 1
+
+    assert check_call.called
+    assert popen.called
+    assert check_output.called
+
+    assert popen.call_args[0][0] == ["docker-compose", "up"]
+    assert popen.call_args[1].get("cwd")
+    assert popen.call_args[1].get("env")
+    assert popen.call_args[1]["env"].get("PREFECT_SERVER_TAG") == version[1]
+
+
 def test_server_start_options_and_flags(monkeypatch, macos_platform):
     check_call = MagicMock()
     popen = MagicMock(side_effect=KeyboardInterrupt())
diff --git a/tests/serialization/test_environments.py b/tests/serialization/test_environments.py
--- a/tests/serialization/test_environments.py
+++ b/tests/serialization/test_environments.py
@@ -11,6 +11,7 @@
     KubernetesJobEnvironmentSchema,
     LocalEnvironmentSchema,
     RemoteEnvironmentSchema,
+    RemoteDaskEnvironmentSchema,
 )
 
 
@@ -253,6 +254,38 @@ def test_serialize_remote_environment_with_labels():
     assert new.labels == set(["bob", "alice"])
 
 
+def test_serialize_remote_dask_environment():
+    env = environments.RemoteDaskEnvironment(address="test")
+
+    schema = RemoteDaskEnvironmentSchema()
+    serialized = schema.dump(env)
+    assert serialized
+    assert serialized["__version__"] == prefect.__version__
+    assert serialized["address"] == "test"
+    assert serialized["labels"] == []
+
+    new = schema.load(serialized)
+    assert new.address == "test"
+    assert new.labels == set()
+
+
+def test_serialize_remote_dask_environment_with_labels():
+    env = environments.RemoteDaskEnvironment(
+        address="test", labels=["bob", "alice"], executor_kwargs={"not": "present"}
+    )
+
+    schema = RemoteDaskEnvironmentSchema()
+    serialized = schema.dump(env)
+    assert serialized
+    assert serialized["__version__"] == prefect.__version__
+    assert serialized["address"] == "test"
+    assert set(serialized["labels"]) == set(["bob", "alice"])
+
+    new = schema.load(serialized)
+    assert new.address == "test"
+    assert new.labels == set(["bob", "alice"])
+
+
 def test_serialize_local_environment_with_labels():
     env = environments.LocalEnvironment(labels=["bob", "alice"])