Repository: PrefectHQ/prefect

Issue:
Docker Version Checker: outlier Docker version format throws exception
### First check

- [X] I added a descriptive title to this issue.
- [X] I used the GitHub search to find a similar issue and didn't find it.
- [X] I searched the Prefect documentation for this issue.
- [X] I checked that this issue is related to Prefect and not one of its dependencies.

### Bug summary

(I also submitted a PR for this: [#8588](https://github.com/PrefectHQ/prefect/pull/8588))

We use SLES linux distributions with Docker version name formats that fail the Prefect version checker: "20.10.17-ce" ("-ce" being the issue)
- State: Prefect uses `packaging.verison.parse` to convert Docker version names into a `Version` object that is used to check for a minimum version (20.10.0 or higher).
- Issue: Our Docker version names - "20.10.17-ce" - is seen as a invalid version string by `packaging.version.parse` (the Enterprise Edition in our linux distress would fail as well, since they end with "-ee"). Because our version isn't failing the intended validation (i.e., lower than what's required) it took some time to figure out why it was failing.

The resolution to this issue in my own environment was pretty straightforward: replace "-ce" or "-ee" with "" for the version name string before passing it into the `parse` method. This wouldn't account for other possible version name outliers, but I did some research and couldn't find any other variants (obviously doesn't mean they don't exist).

I have a PR that resolves this. It puts that logic into a separate function so it can be tested, and so it's easier to add additional outlier version name cases in the future.



### Reproduction

```python3
# pulled from prefect.infrastructure.docker.py, line 676

# `docker_client.version()["Version"]` replaced with relevant "invalid" version
user_version = packaging.version.parse("20.10.17-ce")
```


### Error

```python3
Traceback (most recent call last):
  File "/Users/ben/Documents/github/prefect/_delete.py", line 20, in <module>
    packaging.version.parse("20.10.17-ce")
  File "/Users/ben/Documents/github/prefect/venv/lib/python3.10/site-packages/packaging/version.py", line 52, in parse
    return Version(version)
  File "/Users/ben/Documents/github/prefect/venv/lib/python3.10/site-packages/packaging/version.py", line 197, in __init__
    raise InvalidVersion(f"Invalid version: '{version}'")
packaging.version.InvalidVersion: Invalid version: '20.10.17-ce'
```


### Versions

```Text
# This is from the computer I'm using currently, which isn't really relevant, since the version is only checked with "linux"
# - I get the error results when running on SLES linux, or Windows. The issue is specifically the string format failing to be parsed (which I did run on this computer)
Version:             2.6.6. # <- is this supposed to be different than the 2.8.2 that "pip freeze" shows?
API version:         0.8.3
Python version:      3.9.6
Git commit:          87767cda
Built:               Thu, Nov 3, 2022 1:15 PM
OS/Arch:             darwin/arm64
Profile:             darrida
Server type:         cloud
```


### Additional context

I cloned the Prefect repo at work, adjusted the line in question to the following, built a wheel, deployed a new agent to our test server using that wheel, and the problem with "20.10.17-ce" was resolved. Everything worked fine.

```python
user_version = packaging.version.parse(
    docker_client.version()["Version"].replace("-ce", "").replace("-ee", "")
)
```


Discussion:

Resolving patch:
diff --git a/src/prefect/docker.py b/src/prefect/docker.py
--- a/src/prefect/docker.py
+++ b/src/prefect/docker.py
@@ -491,3 +491,20 @@ def parse_image_tag(name: str) -> Tuple[str, Optional[str]]:
             image_path, tag = image_path.split(":")
         image_name = f"{index_name}/{image_path}"
     return image_name, tag
+
+
+def format_outlier_version_name(version: str):
+    """
+    Formats outlier docker version names to pass `packaging.version.parse` validation
+    - Current cases are simple, but creats stub for more complicated formatting if eventually needed.
+    - Example outlier versions that throw a parsing exception:
+      - "20.10.0-ce" (variant of community edition label)
+      - "20.10.0-ee" (variant of enterprise edition label)
+
+    Args:
+        version (str): raw docker version value
+
+    Returns:
+        str: value that can pass `packaging.version.parse` validation
+    """
+    return version.replace("-ce", "").replace("-ee", "")
diff --git a/src/prefect/infrastructure/docker.py b/src/prefect/infrastructure/docker.py
--- a/src/prefect/infrastructure/docker.py
+++ b/src/prefect/infrastructure/docker.py
@@ -13,7 +13,11 @@
 
 import prefect
 from prefect.blocks.core import Block, SecretStr
-from prefect.docker import get_prefect_image_name, parse_image_tag
+from prefect.docker import (
+    format_outlier_version_name,
+    get_prefect_image_name,
+    parse_image_tag,
+)
 from prefect.exceptions import InfrastructureNotAvailable, InfrastructureNotFound
 from prefect.infrastructure.base import Infrastructure, InfrastructureResult
 from prefect.settings import PREFECT_API_URL
@@ -667,7 +671,9 @@ def _get_extra_hosts(self, docker_client) -> Dict[str, str]:
                 self.env["PREFECT_API_URL"],
             )
         ):
-            user_version = packaging.version.parse(docker_client.version()["Version"])
+            user_version = packaging.version.parse(
+                format_outlier_version_name(docker_client.version()["Version"])
+            )
             required_version = packaging.version.parse("20.10.0")
 
             if user_version < required_version:


Test patch:
diff --git a/tests/docker/test_image_parsing.py b/tests/docker/test_image_parsing.py
--- a/tests/docker/test_image_parsing.py
+++ b/tests/docker/test_image_parsing.py
@@ -1,6 +1,7 @@
+import packaging.version
 import pytest
 
-from prefect.docker import parse_image_tag
+from prefect.docker import format_outlier_version_name, parse_image_tag
 
 
 @pytest.mark.parametrize(
@@ -23,3 +24,22 @@
 )
 def test_parse_image_tag(value, expected):
     assert parse_image_tag(value) == expected
+
+
+@pytest.mark.parametrize(
+    "value,expected",
+    [
+        ("20.10.0", "20.10.0"),
+        ("v20.10.10", "v20.10.10"),
+        ("v20.10.0-ce", "v20.10.0"),
+        ("v20.10.10-ee", "v20.10.10"),
+        ("20.10.0-ce", "20.10.0"),
+        ("20.10.10-ee", "20.10.10"),
+    ],
+)
+def test_format_outlier_version_name(value, expected):
+    version = format_outlier_version_name(value)
+    # Basic test
+    assert version == expected
+    # Confirm return value can be parsed
+    assert isinstance(packaging.version.parse(version), packaging.version.Version)