Repository: gabrieldemarmiesse/python-on-whales (language: python)

Issue:
pydantic_core._pydantic_core.ValidationError: 1 validation error for ServiceInspectResult
When executing docker.stack.services(stack_name)

I get the following error: 
```
Spec.TaskTemplate.ContainerSpec.Privileges.NoNewPrivileges
  Input should be a valid string [type=string_type, input_value=False, input_type=bool]
    For further information visit https://errors.pydantic.dev/2.5/v/string_type
```

The offending output is : 
```json
                    "Privileges": {
                        "CredentialSpec": null,
                        "SELinuxContext": null,
                        "NoNewPrivileges": false
                    },
```

Fully upgraded python-on-whales (from pypi) and docker. 
It looks like the "NoNewPrivileges" has changed from string to bool

To fix it you can change line 
https://github.com/gabrieldemarmiesse/python-on-whales/blob/d8420f923fd6759d4c74a204808473d1472cb538/python_on_whales/components/service/models.py#L23C5-L23C16 -> `privileges: Optional[Dict[str, Any]] = None`

Never worked with pydantic before so I have no clue what the side effects of this are or the reason to enforce it to (optional) str in the first place. 

```bash
$ pip3 list | grep whale
python-on-whales              0.68.0

$ docker version
Client: Docker Engine - Community
 Version:           25.0.2
 API version:       1.44
 Go version:        go1.21.6
 Git commit:        29cf629
 Built:             Thu Feb  1 00:22:57 2024
 OS/Arch:           linux/amd64
 Context:           default

Server: Docker Engine - Community
 Engine:
  Version:          25.0.2
  API version:      1.44 (minimum version 1.24)
  Go version:       go1.21.6
  Git commit:       fce6e0c
  Built:            Thu Feb  1 00:22:57 2024
  OS/Arch:          linux/amd64
  Experimental:     false
 containerd:
  Version:          1.6.28
  GitCommit:        ae07eda36dd25f8a1b98dfbf587313b99c0190bb
 runc:
  Version:          1.1.12
  GitCommit:        v1.1.12-0-g51d5e94
 docker-init:
  Version:          0.19.0
  GitCommit:        de40ad0

```

Full output:
```json
[
    {
        "ID": "u5qxp0uf6ilujoh0w78pgk3c3",
        "Version": {
            "Index": 2388
        },
        "CreatedAt": "2024-02-05T12:31:00.458807712Z",
        "UpdatedAt": "2024-02-05T12:31:00.468744192Z",
        "Spec": {
            "Name": "bepa_internal-multi-flag",
            "Labels": {
                "com.docker.stack.image": "bad-ssh",
                "com.docker.stack.namespace": "bepa"
            },
            "TaskTemplate": {
                "ContainerSpec": {
                    "Image": "bad-ssh:latest",
                    "Labels": {
                        "com.docker.stack.namespace": "bepa"
                    },
                    "Hostname": "internal-multi-flag",
                    "Env": [
                        "FLAGIMFA=bepa#3sspy6QUR2iXhpHkYkkTVRV4OJ2JRi0DoIl5NnhOJAHeJIj3ygTPesuhmSZ",
                        "FLAGIMFB=apabepa"
                    ],
                    "Privileges": {
                        "CredentialSpec": null,
                        "SELinuxContext": null,
                        "NoNewPrivileges": false
                    },
                    "StopGracePeriod": 10000000000,
                    "DNSConfig": {},
                    "Isolation": "default"
                },
                "Resources": {},
                "RestartPolicy": {
                    "Condition": "any",
                    "Delay": 5000000000,
                    "MaxAttempts": 0
                },
                "Placement": {},
                "Networks": [
                    {
                        "Target": "q2gjvz30s7so0aucb7kuxw267",
                        "Aliases": [
                            "internal-multi-flag"
                        ]
                    }
                ],
                "ForceUpdate": 0,
                "Runtime": "container"
            },
            "Mode": {
                "Replicated": {
                    "Replicas": 1
                }
            },
            "UpdateConfig": {
                "Parallelism": 1,
                "FailureAction": "pause",
                "Monitor": 5000000000,
                "MaxFailureRatio": 0,
                "Order": "stop-first"
            },
            "RollbackConfig": {
                "Parallelism": 1,
                "FailureAction": "pause",
                "Monitor": 5000000000,
                "MaxFailureRatio": 0,
                "Order": "stop-first"
            },
            "EndpointSpec": {
                "Mode": "vip"
            }
        },
        "Endpoint": {
            "Spec": {
                "Mode": "vip"
            },
            "VirtualIPs": [
                {
                    "NetworkID": "q2gjvz30s7so0aucb7kuxw267",
                    "Addr": "10.0.1.2/24"
                }
            ]
        }
    }
]
```


Discussion / hints:
Many thanks for the bug report. The right data type should be `privileges: Optional[Dict[str, Union[None, str, bool]]] = None`. I'm open to a pull request to fix it (with the offending json added to the list of json files to try in the unit tests). If no one does it, I'll do it when I have the time.

Fix patch:
diff --git a/python_on_whales/components/service/models.py b/python_on_whales/components/service/models.py
index 84b267b8..ccc332d8 100644
--- a/python_on_whales/components/service/models.py
+++ b/python_on_whales/components/service/models.py
@@ -1,5 +1,5 @@
 from datetime import datetime
-from typing import Any, Dict, List, Optional
+from typing import Any, Dict, List, Optional, Union
 
 from pydantic import Field
 from typing_extensions import Annotated
@@ -20,7 +20,7 @@ class Resources(DockerCamelModel):
 class ContainerSpec(DockerCamelModel):
     image: Optional[str] = None
     labels: Optional[Dict[str, str]] = None
-    privileges: Optional[Dict[str, Optional[str]]] = None
+    privileges: Optional[Dict[str, Union[None, str, bool]]] = None
     stop_grace_period: Optional[int] = None
     isolation: Optional[str] = None
     env: Optional[List[str]] = None


Test patch:
diff --git a/tests/python_on_whales/components/jsons/services/11.json b/tests/python_on_whales/components/jsons/services/11.json
new file mode 100644
index 00000000..7c782aa8
--- /dev/null
+++ b/tests/python_on_whales/components/jsons/services/11.json
@@ -0,0 +1,85 @@
+{
+    "ID": "u5qxp0uf6ilujoh0w78pgk3c3",
+    "Version": {
+        "Index": 2388
+    },
+    "CreatedAt": "2024-02-05T12:31:00.458807712Z",
+    "UpdatedAt": "2024-02-05T12:31:00.468744192Z",
+    "Spec": {
+        "Name": "bepa_internal-multi-flag",
+        "Labels": {
+            "com.docker.stack.image": "bad-ssh",
+            "com.docker.stack.namespace": "bepa"
+        },
+        "TaskTemplate": {
+            "ContainerSpec": {
+                "Image": "bad-ssh:latest",
+                "Labels": {
+                    "com.docker.stack.namespace": "bepa"
+                },
+                "Hostname": "internal-multi-flag",
+                "Env": [
+                    "FLAGIMFB=apabepa"
+                ],
+                "Privileges": {
+                    "CredentialSpec": null,
+                    "SELinuxContext": null,
+                    "NoNewPrivileges": false
+                },
+                "StopGracePeriod": 10000000000,
+                "DNSConfig": {},
+                "Isolation": "default"
+            },
+            "Resources": {},
+            "RestartPolicy": {
+                "Condition": "any",
+                "Delay": 5000000000,
+                "MaxAttempts": 0
+            },
+            "Placement": {},
+            "Networks": [
+                {
+                    "Target": "q2gjvz30s7so0aucb7kuxw267",
+                    "Aliases": [
+                        "internal-multi-flag"
+                    ]
+                }
+            ],
+            "ForceUpdate": 0,
+            "Runtime": "container"
+        },
+        "Mode": {
+            "Replicated": {
+                "Replicas": 1
+            }
+        },
+        "UpdateConfig": {
+            "Parallelism": 1,
+            "FailureAction": "pause",
+            "Monitor": 5000000000,
+            "MaxFailureRatio": 0,
+            "Order": "stop-first"
+        },
+        "RollbackConfig": {
+            "Parallelism": 1,
+            "FailureAction": "pause",
+            "Monitor": 5000000000,
+            "MaxFailureRatio": 0,
+            "Order": "stop-first"
+        },
+        "EndpointSpec": {
+            "Mode": "vip"
+        }
+    },
+    "Endpoint": {
+        "Spec": {
+            "Mode": "vip"
+        },
+        "VirtualIPs": [
+            {
+                "NetworkID": "q2gjvz30s7so0aucb7kuxw267",
+                "Addr": "10.0.1.2/24"
+            }
+        ]
+    }
+}
\ No newline at end of file