Repository: localstack/localstack

Issue:
bug: localstack start --detached fails on windows
### Is there an existing issue for this?

- [X] I have searched the existing issues

### Current Behavior

when running `localstack start --detached` on windows, i get the following error. but the localstack container is started

```
PS C:\Users\thomas> localstack start --detached

C:\Users\thomas>python C:\Users\thomas\AppData\Local\Programs\Python\Python38\Scripts\\localstack start --detached

     __                     _______ __             __
    / /   ____  _________ _/ / ___// /_____ ______/ /__
   / /   / __ \/ ___/ __ `/ /\__ \/ __/ __ `/ ___/ //_/
  / /___/ /_/ / /__/ /_/ / /___/ / /_/ /_/ / /__/ ,<
 /_____/\____/\___/\__,_/_//____/\__/\__,_/\___/_/|_|

 💻 LocalStack CLI 0.13.3.3

[19:44:23] starting LocalStack in Docker mode 🐳                                                       localstack.py:1152022-01-24T19:44:27.581:INFO:bootstrap.py: Execution of "prepare_host" took 3853.20ms
[19:44:27] preparing environment                                                                        bootstrap.py:709[19:44:28] configuring container                                                                        bootstrap.py:717           starting container                                                                           bootstrap.py:723Traceback (most recent call last):
  File "C:\Users\thomas\AppData\Local\Programs\Python\Python38\Scripts\\localstack", line 25, in <module>
    main()
  File "C:\Users\thomas\AppData\Local\Programs\Python\Python38\Scripts\\localstack", line 21, in main
    main.main()
  File "C:\Users\thomas\AppData\Local\Programs\Python\Python38\lib\site-packages\localstack\cli\main.py", line 6, in main
    cli()
  File "C:\Users\thomas\AppData\Local\Programs\Python\Python38\lib\site-packages\localstack\cli\plugin.py", line 15, in __call__
    self.group(*args, **kwargs)
  File "C:\Users\thomas\AppData\Local\Programs\Python\Python38\lib\site-packages\click\core.py", line 1128, in __call__
    return self.main(*args, **kwargs)
  File "C:\Users\thomas\AppData\Local\Programs\Python\Python38\lib\site-packages\click\core.py", line 1053, in main
    rv = self.invoke(ctx)
  File "C:\Users\thomas\AppData\Local\Programs\Python\Python38\lib\site-packages\click\core.py", line 1659, in invoke
    return _process_result(sub_ctx.command.invoke(sub_ctx))
  File "C:\Users\thomas\AppData\Local\Programs\Python\Python38\lib\site-packages\click\core.py", line 1395, in invoke
    return ctx.invoke(self.callback, **ctx.params)
  File "C:\Users\thomas\AppData\Local\Programs\Python\Python38\lib\site-packages\click\core.py", line 754, in invoke
    return __callback(*args, **kwargs)
  File "C:\Users\thomas\AppData\Local\Programs\Python\Python38\lib\site-packages\localstack\cli\localstack.py", line 126, in cmd_start
    bootstrap.start_infra_in_docker_detached(console)
  File "C:\Users\thomas\AppData\Local\Programs\Python\Python38\lib\site-packages\localstack\utils\bootstrap.py", line 726, in start_infra_in_docker_detached
    server.wait_is_container_running()
  File "C:\Users\thomas\AppData\Local\Programs\Python\Python38\lib\site-packages\localstack\utils\bootstrap.py", line 537, in wait_is_container_running
    return poll_condition(self.is_container_running, timeout)
  File "C:\Users\thomas\AppData\Local\Programs\Python\Python38\lib\site-packages\localstack\utils\common.py", line 959, in poll_condition
    while not condition():
  File "C:\Users\thomas\AppData\Local\Programs\Python\Python38\lib\site-packages\localstack\utils\bootstrap.py", line 534, in is_container_running
    return DOCKER_CLIENT.is_container_running(self.container.name)
  File "C:\Users\thomas\AppData\Local\Programs\Python\Python38\lib\site-packages\localstack\utils\docker_utils.py", line 381, in is_container_running
    return container_name in self.get_running_container_names()
  File "C:\Users\thomas\AppData\Local\Programs\Python\Python38\lib\site-packages\localstack\utils\docker_utils.py", line 375, in get_running_container_names
    result = self.list_containers(all=False)
  File "C:\Users\thomas\AppData\Local\Programs\Python\Python38\lib\site-packages\localstack\utils\docker_utils.py", line 661, in list_containers
    container_list = [json.loads(line) for line in cmd_result.splitlines()]
  File "C:\Users\thomas\AppData\Local\Programs\Python\Python38\lib\site-packages\localstack\utils\docker_utils.py", line 661, in <listcomp>
    container_list = [json.loads(line) for line in cmd_result.splitlines()]
  File "C:\Users\thomas\AppData\Local\Programs\Python\Python38\lib\json\__init__.py", line 357, in loads
    return _default_decoder.decode(s)
  File "C:\Users\thomas\AppData\Local\Programs\Python\Python38\lib\json\decoder.py", line 337, in decode
    obj, end = self.raw_decode(s, idx=_w(s, 0).end())
  File "C:\Users\thomas\AppData\Local\Programs\Python\Python38\lib\json\decoder.py", line 353, in raw_decode
    obj, end = self.scan_once(s, idx)
json.decoder.JSONDecodeError: Invalid \escape: line 1 column 124 (char 123)
```

```
PS C:\Users\thomas> docker ps -a
CONTAINER ID   IMAGE                   COMMAND                  CREATED         STATUS         PORTS
                                                  NAMES
53b0bf523ee8   localstack/localstack   "docker-entrypoint.sh"   2 minutes ago   Up 2 minutes   127.0.0.1:4566->4566/tcp, 127.0.0.1:4571->4571/tcp, 127.0.0.1:12121->12121/tcp, 5678/tcp   localstack_main
```

### Expected Behavior

_No response_

### How are you starting LocalStack?

With the `localstack` script

### Steps To Reproduce

`localstack start --detached`

### Environment

```markdown
- OS: Windows 10
- LocalStack: 0.13.3.3
```


### Anything else?

_No response_


Discussion:

Resolving patch:
diff --git a/localstack/utils/docker_utils.py b/localstack/utils/docker_utils.py
--- a/localstack/utils/docker_utils.py
+++ b/localstack/utils/docker_utils.py
@@ -669,10 +669,7 @@ def list_containers(self, filter: Union[List[str], str, None] = None, all=True)
             options += [y for filter_item in filter for y in ["--filter", filter_item]]
         cmd += options
         cmd.append("--format")
-        cmd.append(
-            '{"id":"{{ .ID }}","image":"{{ .Image }}","name":"{{ .Names }}",'
-            '"labels":"{{ .Labels }}","status":"{{ .State }}"}'
-        )
+        cmd.append("{{json . }}")
         try:
             cmd_result = safe_run(cmd).strip()
         except subprocess.CalledProcessError as e:
@@ -682,7 +679,18 @@ def list_containers(self, filter: Union[List[str], str, None] = None, all=True)
         container_list = []
         if cmd_result:
             container_list = [json.loads(line) for line in cmd_result.splitlines()]
-        return container_list
+        result = []
+        for container in container_list:
+            result.append(
+                {
+                    "id": container["ID"],
+                    "image": container["Image"],
+                    "name": container["Names"],
+                    "status": container["State"],
+                    "labels": container["Labels"],
+                }
+            )
+        return result
 
     def copy_into_container(
         self, container_name: str, local_path: str, container_path: str
@@ -1546,16 +1554,19 @@ def wait_for_result(*_):
                 start_waiting.set()
 
                 # handle container input/output
-                with sock:
-                    try:
-                        if stdin:
-                            sock.sendall(to_bytes(stdin))
-                            sock.shutdown(socket.SHUT_WR)
-                        stdout, stderr = self._read_from_sock(sock, False)
-                    except socket.timeout:
-                        LOG.debug(
-                            f"Socket timeout when talking to the I/O streams of Docker container '{container_name_or_id}'"
-                        )
+                # under windows, the socket has no __enter__ / cannot be used as context manager
+                # therefore try/finally instead of with here
+                try:
+                    if stdin:
+                        sock.sendall(to_bytes(stdin))
+                        sock.shutdown(socket.SHUT_WR)
+                    stdout, stderr = self._read_from_sock(sock, False)
+                except socket.timeout:
+                    LOG.debug(
+                        f"Socket timeout when talking to the I/O streams of Docker container '{container_name_or_id}'"
+                    )
+                finally:
+                    sock.close()
 
                 # get container exit code
                 exit_code = result_queue.get()


Test patch:
diff --git a/tests/unit/test_dockerclient.py b/tests/unit/test_dockerclient.py
--- a/tests/unit/test_dockerclient.py
+++ b/tests/unit/test_dockerclient.py
@@ -20,19 +20,26 @@ def _docker_cmd(self) -> List[str]:
 
     @patch("localstack.utils.docker_utils.safe_run")
     def test_list_containers(self, run_mock):
-        test_container = {
-            "id": "00000000a1",
-            "image": "localstack/localstack",
-            "name": "localstack_main",
-            "labels": "authors=LocalStack Contributors",
-            "status": "running",
+        mock_container = {
+            "ID": "00000000a1",
+            "Image": "localstack/localstack",
+            "Names": "localstack_main",
+            "Labels": "authors=LocalStack Contributors",
+            "State": "running",
         }
-        run_mock.return_value = json.dumps(test_container)
+        return_container = {
+            "id": mock_container["ID"],
+            "image": mock_container["Image"],
+            "name": mock_container["Names"],
+            "labels": mock_container["Labels"],
+            "status": mock_container["State"],
+        }
+        run_mock.return_value = json.dumps(mock_container)
         docker_client = CmdDockerClient()
         container_list = docker_client.list_containers()
         call_arguments = run_mock.call_args[0][0]
         LOG.info("Intercepted call arguments: %s", call_arguments)
-        self.assertEqual(test_container, container_list[0])
+        self.assertEqual(return_container, container_list[0])
         self.assertTrue(list_in(self._docker_cmd() + ["ps"], call_arguments))
         self.assertIn("-a", call_arguments)
         self.assertIn("--format", call_arguments)