Repository: localstack/localstack

Issue:
bug: Localstack fails to start with cli 0.14.3.4
### Is there an existing issue for this?

- [X] I have searched the existing issues

### Current Behavior

```console
     __                     _______ __             __
    / /   ____  _________ _/ / ___// /_____ ______/ /__
   / /   / __ \/ ___/ __ `/ /\__ \/ __/ __ `/ ___/ //_/
  / /___/ /_/ / /__/ /_/ / /___/ / /_/ /_/ / /__/ ,<
 /_____/\____/\___/\__,_/_//____/\__/\__,_/\___/_/|_|
 💻 LocalStack CLI 0.14.3.4
[14:47:24] starting LocalStack in Docker mode 🐳               localstack.py:135
           preparing environment                                bootstrap.py:729
           configuring container                                bootstrap.py:737
Traceback (most recent call last):
  File "/home/runner/.local/bin/localstack", line 23, in <module>
    main()
  File "/home/runner/.local/bin/localstack", line 19, in main
    main.main()
  File "/home/runner/.local/lib/python3.8/site-packages/localstack/cli/main.py", line 6, in main
    cli()
  File "/home/runner/.local/lib/python3.8/site-packages/localstack/cli/plugin.py", line 15, in __call__
    self.group(*args, **kwargs)
  File "/usr/lib/python3/dist-packages/click/core.py", line 764, in __call__
    return self.main(*args, **kwargs)
  File "/usr/lib/python3/dist-packages/click/core.py", line 717, in main
    rv = self.invoke(ctx)
  File "/usr/lib/python3/dist-packages/click/core.py", line 1137, in invoke
    return _process_result(sub_ctx.command.invoke(sub_ctx))
  File "/usr/lib/python3/dist-packages/click/core.py", line 956, in invoke
    return ctx.invoke(self.callback, **ctx.params)
  File "/usr/lib/python3/dist-packages/click/core.py", line 555, in invoke
    return callback(*args, **kwargs)
  File "/home/runner/.local/lib/python3.8/site-packages/localstack/utils/analytics/cli.py", line 66, in publisher_wrapper
    return fn(*args, **kwargs)
  File "/home/runner/.local/lib/python3.8/site-packages/localstack/cli/localstack.py", line 146, in cmd_start
    bootstrap.start_infra_in_docker_detached(console)
  File "/home/runner/.local/lib/python3.8/site-packages/localstack/utils/bootstrap.py", line 739, in start_infra_in_docker_detached
    configure_container(container)
  File "/home/runner/.local/lib/python3.8/site-packages/localstack/utils/bootstrap.py", line 611, in configure_container
    hooks.configure_localstack_container.run(container)
  File "/home/runner/.local/lib/python3.8/site-packages/localstack/runtime/hooks.py", line 62, in run_in_order
    fn_plugin(*args, **kwargs)
  File "/home/runner/.local/lib/python3.8/site-packages/plugin/core.py", line 183, in __call__
    return self.fn(*args, **kwargs)
  File "/home/runner/.local/lib/python3.8/site-packages/localstack/plugins.py", line 15, in configure_edge_port
    container.ports.add(port)
  File "/home/runner/.local/lib/python3.8/site-packages/localstack/utils/container_utils/container_client.py", line 122, in add
    if isinstance(port, PortRange):
  File "/usr/lib/python3.8/typing.py", line 769, in __instancecheck__
    return self.__subclasscheck__(type(obj))
  File "/usr/lib/python3.8/typing.py", line 777, in __subclasscheck__
    raise TypeError("Subscripted generics cannot be used with"
TypeError: Subscripted generics cannot be used with class and instance checks
Waiting for LocalStack startup...
Error: timeout
Startup complete
```

### Expected Behavior

Succeed starting localstack

### How are you starting LocalStack?

With the `localstack` script

### Steps To Reproduce

#### How are you starting localstack (e.g., `bin/localstack` command, arguments, or `docker-compose.yml`)

    localstack start

Build: https://github.com/functionless/functionless/runs/7025965587?check_suite_focus=true

Was re-run twice, recreated on my local 

### Environment

```markdown
- OS: WSL 2 - Ubuntu 20.04
- LocalStack: latest
- Cli: 0.14.3.4
```


### Anything else?

_No response_


Discussion:
Ran the same setup with `0.14.3.3` and it succeeded.
thanks for reporting Sam @thantos, looking into it. can you pin the version to 0.14.3.3 for now?
Yeah, can do.
Seems like related to this python 3.10 feature https://peps.python.org/pep-0604/ which is not available in python 3.8, but we use it in the recent refactoring of docker client.

Resolving patch:
diff --git a/localstack/utils/container_utils/container_client.py b/localstack/utils/container_utils/container_client.py
--- a/localstack/utils/container_utils/container_client.py
+++ b/localstack/utils/container_utils/container_client.py
@@ -14,9 +14,9 @@
 from typing import Dict, List, Optional, Tuple, Union
 
 if sys.version_info >= (3, 8):
-    from typing import Protocol
+    from typing import Protocol, get_args
 else:
-    from typing_extensions import Protocol
+    from typing_extensions import Protocol, get_args
 
 from localstack import config
 from localstack.utils.collections import HashableList
@@ -100,6 +100,14 @@ def close(self):
 PortRange = Union[List, HashableList]
 
 
+def isinstance_union(obj, class_or_tuple):
+    # that's some dirty hack
+    if sys.version_info < (3, 10):
+        return isinstance(obj, get_args(PortRange))
+    else:
+        return isinstance(obj, class_or_tuple)
+
+
 class PortMappings:
     """Maps source to target port ranges for Docker port mappings."""
 
@@ -119,9 +127,9 @@ def add(
         protocol: str = "tcp",
     ):
         mapped = mapped or port
-        if isinstance(port, PortRange):
+        if isinstance_union(port, PortRange):
             for i in range(port[1] - port[0] + 1):
-                if isinstance(mapped, PortRange):
+                if isinstance_union(mapped, PortRange):
                     self.add(port[0] + i, mapped[0] + i)
                 else:
                     self.add(port[0] + i, mapped)


Test patch: