From 2006d16b30849c04e511b7b291e692141e00b2bb Mon Sep 17 00:00:00 2001
From: Luca Bilke <luca@bil.ke>
Date: Wed, 23 Oct 2024 17:15:37 +0200
Subject: [PATCH] WIP

---
 plugins/module_utils/common.py                | 205 +++++++++++++-----
 .../modules/label_docker_volume_backupper.py  |  51 +++++
 plugins/modules/label_traefik_middleware.py   |  60 +++++
 plugins/modules/label_traefik_router.py       |  83 +++++++
 plugins/modules/label_traefik_service.py      |  64 ++++++
 plugins/modules/service.py                    |  90 ++++++++
 plugins/modules/service_docker_in_docker.py   |  48 ++++
 .../modules/service_docker_socket_proxy.py    |  54 +++++
 ....py => service_docker_volume_backupper.py} |   4 +-
 .../{mariadb.py => service_mariadb.py}        |   6 +-
 .../{postgres.py => service_postgres.py}      |   6 +-
 .../modules/{redis.py => service_redis.py}    |   0
 plugins/pyproject.toml                        |   2 +
 13 files changed, 612 insertions(+), 61 deletions(-)
 create mode 100644 plugins/modules/label_docker_volume_backupper.py
 create mode 100644 plugins/modules/label_traefik_middleware.py
 create mode 100644 plugins/modules/label_traefik_router.py
 create mode 100644 plugins/modules/label_traefik_service.py
 create mode 100644 plugins/modules/service.py
 create mode 100644 plugins/modules/service_docker_in_docker.py
 create mode 100644 plugins/modules/service_docker_socket_proxy.py
 rename plugins/modules/{docker_volume_backupper.py => service_docker_volume_backupper.py} (93%)
 rename plugins/modules/{mariadb.py => service_mariadb.py} (94%)
 rename plugins/modules/{postgres.py => service_postgres.py} (93%)
 rename plugins/modules/{redis.py => service_redis.py} (100%)

diff --git a/plugins/module_utils/common.py b/plugins/module_utils/common.py
index fdd2bd7..f0fc26d 100644
--- a/plugins/module_utils/common.py
+++ b/plugins/module_utils/common.py
@@ -32,6 +32,21 @@ BASE_SERVICE_ARGS = {
         "type": "dict",
     },
 }
+BASE_LABEL_ARGS = {
+    "project_name": {
+        "type": "str",
+        "required": True,
+    },
+    "name": {
+        "type": "str",
+        "required": True,
+    },
+    "state": {
+        "type": "str",
+        "default": "present",
+        "choices": ["present", "absent"],
+    },
+}
 
 
 @dataclass(frozen=True)
@@ -51,7 +66,7 @@ class State:
     result: Result
     compose_filepath: str
     before: dict[str, Any]
-    after: dict[str, Any] = field(default_factory=dict)
+    after: dict[str, Any]
 
 
 def _recursive_update(default: dict[Any, Any], update: dict[Any, Any]) -> dict[Any, Any]:
@@ -85,64 +100,119 @@ def get_state(module: AnsibleModule) -> State:
         result=Result(),
         compose_filepath=compose_filepath,
         before=before,
+        after=before,
     )
 
 
 def apply_service_base(state: State) -> State:
-    params = state.module.params
+    service_name = state.module.params["name"]
+    project_name = state.module.params["project_name"]
+    image = state.module.params["image"]
 
-    compose = copy.deepcopy(state.before)
-    services = compose.get("services", {})
+    update: dict[str, Any] = {
+        "service_name": f"{project_name}_{service_name}",
+        "hostname": f"{project_name}_{service_name}",
+        "image": image,
+        "restart": "unless-stopped",
+        "environment": {},
+        "labels": {},
+        "volumes": [],
+        "networks": {
+            f"{project_name}_internal": None,
+        },
+    }
 
-    networks = compose.get("networks", {}).update(
-        {
-            f"{params['project_name']}_internal": None,
-        }
-    )
-
-    service = services.get(params["name"], {}).update(
-        {
-            "container_name": f"{params['project_name']}_{params['name']}",
-            "hostname": f"{params['project_name']}_{params['name']}",
-            "image": params["image"],
-            "restart": "unless-stopped",
-            "environment": {},
-            "labels": {},
-            "volumes": [],
-            "networks": {
-                f"{params['project_name']}_internal": None,
-            },
-        }
-    )
-
-    services.update(
-        {
-            "networks": networks,
-            params["name"]: service,
-        }
-    )
-
-    return replace(state, after=compose)
+    return update_service(state, update)
 
 
-def set_defaults(state: State) -> State:
-    params = state.module.params
-    compose = copy.deepcopy(state.before)
-    services = compose["services"]
-    service = services[params["name"]]
+def set_service_defaults(state: State) -> State:
+    container_name = state.module.params["name"]
+    defaults = state.module.params["defaults"]
+    project = copy.deepcopy(state.after)
+    services = project["services"]
+    service = services[container_name]
 
-    _recursive_update(service, params["defaults"])
+    _ = _recursive_update(service, defaults)
 
-    services.update({params["name"]: service})
+    services.update({container_name: service})
 
-    return replace(state, after=compose)
+    return replace(state, after=project)
 
 
 def update_service(state: State, update: dict[str, Any]) -> State:
-    compose = copy.deepcopy(state.before)
-    name = state.module.params["name"]
-    _recursive_update(compose["services"][name], update)
-    return replace(state, after=compose)
+    project = copy.deepcopy(state.after)
+    service_name = state.module.params["name"]
+
+    _ = _recursive_update(project["services"][service_name], update)
+
+    return replace(state, after=project)
+
+
+def remove_service(state: State) -> State:
+    project = copy.deepcopy(state.after)
+    service_name = state.module.params["name"]
+
+    del project["services"][service_name]
+
+    return replace(state, after=project)
+
+
+def remove_labels(state: State, label_names: list[str]) -> State:
+    project = copy.deepcopy(state.after)
+    service_name = state.module.params["name"]
+    service = project["services"].get(service_name, {})
+
+    labels = service.get("labels", {})
+
+    if labels:
+        for label in labels:
+            if label in label_names:
+                try:
+                    del service["labels"][label]
+                except KeyError:
+                    pass
+
+        service["labels"] = labels
+
+    else:
+        try:
+            del service["labels"]
+        except KeyError:
+            pass
+
+    project["services"][service_name] = service
+
+    return replace(state, after=project)
+
+
+def update_project(state: State) -> State:
+    project = copy.deepcopy(state.after)
+
+    project_services = project.get("services", {})
+    project_networks = project.get("networks", {})
+    project_volumes = project.get("volumes", {})
+
+    for service in project_services:
+        if service_volumes := service.get("volumes"):
+            service_volume_names = [x["source"] for x in service_volumes]
+            project_volumes.update(
+                {
+                    service_volume_name: None
+                    for service_volume_name in service_volume_names
+                    if service_volume_name not in project_volumes
+                }
+            )
+
+        if service_network_names := service.get("networks").keys():
+            project_networks.update(
+                {
+                    service_network_name: None
+                    for service_network_name in service_network_names
+                    if service_network_name not in project_networks
+                }
+            )
+
+    return replace(state, after=project)
 
 
 def write_compose(state: State) -> State:
@@ -156,21 +226,50 @@ def write_compose(state: State) -> State:
 
 def run_service(
     extra_args: dict[str, Any] = {},
-    helper: Callable[[State], State] = lambda x: x,
+    helper: Callable[[State], State] = lambda _: _,
 ) -> None:
     module = AnsibleModule(
-        argument_spec=BASE_SERVICE_ARGS.update(extra_args),
+        argument_spec={**BASE_SERVICE_ARGS, **extra_args},
         supports_check_mode=True,
     )
 
     state = get_state(module)
 
-    for f in [apply_service_base, set_defaults, helper]:
-        state = f(state)
+    if module.params["state"] == "absent":
+        state = remove_service(state)
 
-    if module.check_mode:
-        module.exit_json(**asdict(state.result))  # type: ignore[reportUnkownMemberType]
+    else:
+        for f in [apply_service_base, set_service_defaults, helper, update_project]:
+            state = f(state)
 
-    write_compose(state)
+    exit(state)
 
-    module.exit_json(**asdict(state.result))  # type: ignore[reportUnkownMemberType]
+
+def run_label(
+    extra_args: dict[str, Any], helper: Callable[[State], State], label_names: list[str]
+) -> None:
+    module = AnsibleModule(
+        argument_spec={**BASE_LABEL_ARGS, **extra_args},
+        supports_check_mode=True,
+    )
+
+    state = get_state(module)
+
+    if module.params["state"] == "absent":
+        state = remove_labels(state, label_names)
+
+    else:
+        state = helper(state)
+
+    exit(state)
+
+
+def exit(state: State) -> None:
+    # TODO: Check diff and set changed variable
+
+    if state.module.check_mode:
+        state.module.exit_json(**asdict(state.result))  # type: ignore[reportUnkownMemberType]
+
+    _ = write_compose(state)
+
+    state.module.exit_json(**asdict(state.result))  # type: ignore[reportUnkownMemberType]
diff --git a/plugins/modules/label_docker_volume_backupper.py b/plugins/modules/label_docker_volume_backupper.py
new file mode 100644
index 0000000..63aed2b
--- /dev/null
+++ b/plugins/modules/label_docker_volume_backupper.py
@@ -0,0 +1,51 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2024, Luca Bilke <luca@bil.ke>
+# MIT License (see LICENSE)
+
+# TODO: write ansible sections
+
+DOCUMENTATION = r"""
+"""
+
+EXAMPLES = r"""
+"""
+
+RETURN = r"""
+"""
+
+from __future__ import annotations  # pyright: ignore[reportGeneralTypeIssues]
+
+from typing import Any
+
+from ansible_collections.snailed.ez_compose.plugins.module_utils.common import (
+    State,
+    run_label,
+    update_service,
+)
+
+
+def helper(state: State) -> State:
+    stop = state.module.params.get("stop", True)
+    project_name = state.module.params["project_name"]
+
+    update: dict[str, Any] = {}
+
+    if stop:
+        update["labels"] = {
+            "docker-volume-backup.stop-during-backup": project_name,
+        }
+
+    return update_service(state, update)
+
+
+def main():
+    extra_args = {
+        "stop": {"type": "bool"},
+    }
+    run_label(extra_args, helper)
+
+
+if __name__ == "__main__":
+    main()
diff --git a/plugins/modules/label_traefik_middleware.py b/plugins/modules/label_traefik_middleware.py
new file mode 100644
index 0000000..99b17e3
--- /dev/null
+++ b/plugins/modules/label_traefik_middleware.py
@@ -0,0 +1,60 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2024, Luca Bilke <luca@bil.ke>
+# MIT License (see LICENSE)
+
+# TODO: write ansible sections
+
+DOCUMENTATION = r"""
+"""
+
+EXAMPLES = r"""
+"""
+
+RETURN = r"""
+"""
+
+from __future__ import annotations  # pyright: ignore[reportGeneralTypeIssues]
+
+from ansible_collections.snailed.ez_compose.plugins.module_utils.common import (
+    State,
+    run_label,
+    update_service,
+)
+
+
+def helper(state: State) -> State:
+    service_name = state.module.params["name"]
+    project_name = state.module.params["project_name"]
+    middleware = state.module.params["middleware"]
+    settings = state.module.params["settings"]
+    proxy_type = state.module.params.get("proxy_type", "http")
+    middleware_name = state.module.params.get(
+        "middleware_name",
+        f"{project_name}_{service_name}_{proxy_type}_{middleware.lower()}",
+    )
+
+    prefix = f"traefik.{proxy_type}.middlewares.{middleware_name}"
+
+    labels = {f"{prefix}.{middleware}.{key}": var for key, var in settings.items()}
+
+    update = {
+        "labels": labels,
+    }
+
+    return update_service(state, update)
+
+
+def main():
+    extra_args = {
+        "proxy_type": {"type": "str"},
+        "middleware_name": {"type": "string"},
+        "middleware": {"type": "str", "required": True},
+        "settings": {"type": "list", "required": True},
+    }
+    run_label(extra_args, helper)
+
+
+if __name__ == "__main__":
+    main()
diff --git a/plugins/modules/label_traefik_router.py b/plugins/modules/label_traefik_router.py
new file mode 100644
index 0000000..9272e80
--- /dev/null
+++ b/plugins/modules/label_traefik_router.py
@@ -0,0 +1,83 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2024, Luca Bilke <luca@bil.ke>
+# MIT License (see LICENSE)
+
+# TODO: write ansible sections
+
+DOCUMENTATION = r"""
+"""
+
+EXAMPLES = r"""
+"""
+
+RETURN = r"""
+"""
+
+from __future__ import annotations  # pyright: ignore[reportGeneralTypeIssues]
+
+from ansible_collections.snailed.ez_compose.plugins.module_utils.common import (
+    State,
+    run_label,
+    update_service,
+)
+
+
+def helper(state: State) -> State:
+    service_name = state.module.params["name"]
+    project_name = state.module.params["project_name"]
+    rule = state.module.params["rule"]
+    service = state.module.params.get("service")
+    entrypoints = state.module.params.get("entrypoints")
+    middlewares = state.module.params.get("middlewares")
+    certresolver = state.module.params.get("certresolver")
+    proxy_type = state.module.params.get("proxy_type", "http")
+    router_name = state.module.params.get(
+        "router_name",
+        f"{project_name}_{service_name}_{proxy_type}",
+    )
+
+    prefix = f"traefik.{proxy_type}.routers.{router_name}"
+
+    labels = {
+        "traefik.enable": True,
+        f"traefik.{prefix}.rule": rule,
+    }
+
+    if certresolver:
+        labels[f"traefik.{prefix}.tls.certresolver"] = certresolver
+
+    if entrypoints:
+        labels[f"{prefix}.entrypoints"] = ",".join(entrypoints)
+
+    if service:
+        labels[f"{prefix}.service"] = service
+
+    if middlewares:
+        labels[f"{prefix}.middlewares"] = ",".join(middlewares)
+
+    update = {
+        "labels": labels,
+    }
+
+    return update_service(state, update)
+
+
+def main():
+    extra_args = {
+        "proxy_type": {"type": "str"},
+        "router_name": {
+            "type": "str",
+        },
+        "rule": {"type": "str", "required": True},
+        "service": {"type": "str"},
+        "certresolver": {"type": "str"},
+        "entrypoints": {"type": "list"},
+        "middlewares": {"type": "list"},
+    }
+    run_label(extra_args, helper)
+
+
+if __name__ == "__main__":
+    main()
diff --git a/plugins/modules/label_traefik_service.py b/plugins/modules/label_traefik_service.py
new file mode 100644
index 0000000..5de9af2
--- /dev/null
+++ b/plugins/modules/label_traefik_service.py
@@ -0,0 +1,64 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2024, Luca Bilke <luca@bil.ke>
+# MIT License (see LICENSE)
+
+# TODO: write ansible sections
+
+DOCUMENTATION = r"""
+"""
+
+EXAMPLES = r"""
+"""
+
+RETURN = r"""
+"""
+
+from __future__ import annotations  # pyright: ignore[reportGeneralTypeIssues]
+
+from ansible_collections.snailed.ez_compose.plugins.module_utils.common import (
+    State,
+    run_label,
+    update_service,
+)
+
+
+from typing import Any
+
+
+def helper(state: State) -> State:
+    service_name = state.module.params["name"]
+    project_name = state.module.params["project_name"]
+    port = state.module.params.get("port")
+    proxy_type = state.module.params.get("proxy_type", "http")
+    service_name = state.module.params.get(
+        "service_name",
+        f"{project_name}_{service_name}_{proxy_type}",
+    )
+
+    prefix = f"traefik.{proxy_type}.services.{service_name}"
+
+    labels: dict[str, Any] = {}
+
+    if port:
+        labels[f"{prefix}.loadbalancer.server.port"] = str(port)
+
+    update = {
+        "labels": labels,
+    }
+
+    return update_service(state, update)
+
+
+def main():
+    extra_args = {
+        "proxy_type": {"type": "str"},
+        "service_name": {"type": "string"},
+        "port": {"type": "int"},
+    }
+    run_label(extra_args, helper)
+
+
+if __name__ == "__main__":
+    main()
diff --git a/plugins/modules/service.py b/plugins/modules/service.py
new file mode 100644
index 0000000..048af58
--- /dev/null
+++ b/plugins/modules/service.py
@@ -0,0 +1,90 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2024, Luca Bilke <luca@bil.ke>
+# MIT License (see LICENSE)
+
+# TODO: write ansible sections
+
+DOCUMENTATION = r"""
+"""
+
+EXAMPLES = r"""
+"""
+
+RETURN = r"""
+"""
+
+from __future__ import annotations  # pyright: ignore[reportGeneralTypeIssues]
+
+import copy
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.snailed.ez_compose.plugins.module_utils.common import (
+    State,
+    exit,
+    get_state,
+    remove_service,
+    update_project,
+    update_service,
+)
+
+
+def helper(state: State) -> State:
+    project_name = state.module.params["project_name"]
+    definition = state.module.params["definition"]
+    internal_network = state.module.params.get("internal_network", False)
+
+    update = copy.deepcopy(definition)
+
+    networks = update.get("networks", {})
+
+    if internal_network:
+        networks[f"{project_name}_internal"] = None
+
+    update["networks"] = networks
+
+    return update_service(state, update)
+
+
+def main():
+    module = AnsibleModule(
+        argument_spec={
+            "project_name": {
+                "type": "str",
+                "required": True,
+            },
+            "name": {
+                "type": "str",
+                "required": True,
+            },
+            "internal_network": {
+                "type": "bool",
+            },
+            "state": {
+                "type": "str",
+                "default": "present",
+                "choices": ["present", "absent"],
+            },
+            "definition": {
+                "type": "dict",
+                "required": True,
+            },
+        },
+        supports_check_mode=True,
+    )
+
+    state = get_state(module)
+
+    if module.params["state"] == "absent":
+        state = remove_service(state)
+
+    else:
+        for f in [helper, update_project]:
+            state = f(state)
+
+    exit(state)
+
+
+if __name__ == "__main__":
+    main()
diff --git a/plugins/modules/service_docker_in_docker.py b/plugins/modules/service_docker_in_docker.py
new file mode 100644
index 0000000..f516cf1
--- /dev/null
+++ b/plugins/modules/service_docker_in_docker.py
@@ -0,0 +1,48 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2024, Luca Bilke <luca@bil.ke>
+# MIT License (see LICENSE)
+
+# TODO: write ansible sections
+
+DOCUMENTATION = r"""
+"""
+
+EXAMPLES = r"""
+"""
+
+RETURN = r"""
+"""
+
+from __future__ import annotations  # pyright: ignore[reportGeneralTypeIssues]
+
+from ansible_collections.snailed.ez_compose.plugins.module_utils.common import (
+    State,
+    run_service,
+    update_service,
+)
+
+
+def helper(state: State) -> State:
+    command = state.module.params.get("command")
+
+    update = {
+        "privileged": True,
+    }
+
+    if command:
+        update["command"] = command
+
+    return update_service(state, update)
+
+
+def main():
+    extra_args = {
+        "command": {"type": "list"},
+    }
+    run_service(extra_args, helper)
+
+
+if __name__ == "__main__":
+    main()
diff --git a/plugins/modules/service_docker_socket_proxy.py b/plugins/modules/service_docker_socket_proxy.py
new file mode 100644
index 0000000..3161c81
--- /dev/null
+++ b/plugins/modules/service_docker_socket_proxy.py
@@ -0,0 +1,54 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2024, Luca Bilke <luca@bil.ke>
+# MIT License (see LICENSE)
+
+# TODO: write ansible sections
+
+DOCUMENTATION = r"""
+"""
+
+EXAMPLES = r"""
+"""
+
+RETURN = r"""
+"""
+
+from __future__ import annotations  # pyright: ignore[reportGeneralTypeIssues]
+
+from ansible_collections.snailed.ez_compose.plugins.module_utils.common import (
+    State,
+    run_service,
+    update_service,
+)
+
+
+def helper(state: State) -> State:
+    read_only = state.module.params.get("read_only", True)
+
+    volumes = [
+        {
+            "type": "bind",
+            "source": "/var/run/docker.sock",
+            "target": "/var/run/docker.sock",
+            "read_only": read_only,
+        }
+    ]
+
+    update = {
+        "volumes": volumes,
+    }
+
+    return update_service(state, update)
+
+
+def main():
+    extra_args = {
+        "read_only": {"type": "bool"},
+    }
+    run_service(extra_args, helper)
+
+
+if __name__ == "__main__":
+    main()
diff --git a/plugins/modules/docker_volume_backupper.py b/plugins/modules/service_docker_volume_backupper.py
similarity index 93%
rename from plugins/modules/docker_volume_backupper.py
rename to plugins/modules/service_docker_volume_backupper.py
index ed87a2b..309bafa 100644
--- a/plugins/modules/docker_volume_backupper.py
+++ b/plugins/modules/service_docker_volume_backupper.py
@@ -27,7 +27,7 @@ from ansible_collections.snailed.ez_compose.plugins.module_utils.common import (
 def helper(state: State) -> State:
     archive = state.module.params.get("archive")
     backup_volumes = state.module.params.get("backup_volumes", [])
-    name = state.module.params["name"]
+    service_name = state.module.params["name"]
     project_name = state.module.params["project_name"]
 
     volumes = [
@@ -46,7 +46,7 @@ def helper(state: State) -> State:
         "BACKUP_PRUNING_PREFIX": f"{project_name}-",
         "BACKUP_STOP_DURING_BACKUP_LABEL": project_name,
         "BACKUP_ARCHIVE": "/archive",
-        "DOCKER_HOST": f"tcp://{project_name}_{name}_socket_proxy:2375",
+        "DOCKER_HOST": f"tcp://{project_name}_{service_name}_socket_proxy:2375",
     }
 
     if archive:
diff --git a/plugins/modules/mariadb.py b/plugins/modules/service_mariadb.py
similarity index 94%
rename from plugins/modules/mariadb.py
rename to plugins/modules/service_mariadb.py
index b1a012f..486e328 100644
--- a/plugins/modules/mariadb.py
+++ b/plugins/modules/service_mariadb.py
@@ -32,12 +32,12 @@ def helper(state: State) -> State:
     username = state.module.params["username"]
     password = state.module.params["password"]
     root_password = state.module.params["root_password"]
-    name = state.module.params["name"]
+    service_name = state.module.params["name"]
     project_name = state.module.params["project_name"]
 
     volumes = [
         {
-            "source": name,
+            "source": service_name,
             "target": "/var/lib/mysql",
             "type": "volume",
         }
@@ -75,7 +75,7 @@ def helper(state: State) -> State:
         volumes.append(
             {
                 "type": "volume",
-                "source": f"{name}_backup",
+                "source": f"{service_name}_backup",
                 "target": "/backup",
             }
         )
diff --git a/plugins/modules/postgres.py b/plugins/modules/service_postgres.py
similarity index 93%
rename from plugins/modules/postgres.py
rename to plugins/modules/service_postgres.py
index 564d0aa..67da053 100644
--- a/plugins/modules/postgres.py
+++ b/plugins/modules/service_postgres.py
@@ -31,12 +31,12 @@ def helper(state: State) -> State:
     database = state.module.params["database"]
     username = state.module.params["username"]
     password = state.module.params["password"]
-    name = state.module.params["name"]
+    service_name = state.module.params["name"]
     project_name = state.module.params["project_name"]
 
     volumes = [
         {
-            "source": name,
+            "source": service_name,
             "target": "/var/lib/postgresql/data",
             "type": "volume",
         }
@@ -67,7 +67,7 @@ def helper(state: State) -> State:
         volumes.append(
             {
                 "type": "volume",
-                "source": f"{name}_backup",
+                "source": f"{service_name}_backup",
                 "target": "/backup",
             }
         )
diff --git a/plugins/modules/redis.py b/plugins/modules/service_redis.py
similarity index 100%
rename from plugins/modules/redis.py
rename to plugins/modules/service_redis.py
diff --git a/plugins/pyproject.toml b/plugins/pyproject.toml
index 659b4b4..b4d93b0 100644
--- a/plugins/pyproject.toml
+++ b/plugins/pyproject.toml
@@ -4,11 +4,13 @@ line-length = 100
 [tool.basedpyright]
 typeCheckingMode = "strict"
 reportIgnoreCommentWithoutRule = true
+reportUnusedCallResult = true
 reportMissingTypeStubs = false
 
 # handled by ruff
 reportUnusedVariable = false
 reportUnusedImport = false
+reportUndefinedVariable = false
 
 
 [tool.ruff.lint]