Repository: goauthentik/authentik

Issue:
Outpost Controller task (and thus respective pods) not starting when Authentik is started with helm chart
### Discussed in https://github.com/goauthentik/authentik/discussions/9418

<div type='discussions-op-text'>

<sup>Originally posted by **hamlet42** April 25, 2024</sup>
I am using a Authentik instance with the helm chart under K3s. 

When I start Authentik with the helm chart, the respective Outpost pods are not created automatically. Reasons seems to be that the  respective system tasks (Outpost Controller) per Outpost are not starting automatically, only the one for the embedded outpost. The outposts are shown unhealthy in the dashboard overview page.

This worked for me until the 2023.x releases and stopped working, I think, with the 2024.x releases for me. 

I created logs on DEBUG level and checked them but no errors that pointed me to the root cause.

To reproduce the behavior:
1. Bring authentik down with "helm uninstall authentik"
2. Delete outpost pods with kubectl scale --replicas=0 deployment/ak-outpost-xxx
3.  Bring authentik up with "helm install authentik"

Workaround: 
Only if I open the config of the outpost and click "Update" (not changing anything in the config), the respective system task "outpost_controller: xxx" is created as can be seen in the system tasks page and the respective outpost pod is created.

Can some confirm that change in the behavior? Or did I miss a setting that is now required and not set, eg. from the breaking changes coming with 2024.x?

Any ideas or feedback appreciated.
Regs Helmut</div>


Discussion:

Resolving patch:
diff --git a/authentik/events/system_tasks.py b/authentik/events/system_tasks.py
--- a/authentik/events/system_tasks.py
+++ b/authentik/events/system_tasks.py
@@ -119,7 +119,7 @@ def on_failure(self, exc, task_id, args, kwargs, einfo):
                 "task_call_kwargs": sanitize_item(kwargs),
                 "status": self._status,
                 "messages": sanitize_item(self._messages),
-                "expires": now() + timedelta(hours=self.result_timeout_hours),
+                "expires": now() + timedelta(hours=self.result_timeout_hours + 3),
                 "expiring": True,
             },
         )
diff --git a/authentik/root/settings.py b/authentik/root/settings.py
--- a/authentik/root/settings.py
+++ b/authentik/root/settings.py
@@ -376,7 +376,13 @@
     "task_default_queue": "authentik",
     "broker_url": CONFIG.get("broker.url") or redis_url(CONFIG.get("redis.db")),
     "result_backend": CONFIG.get("result_backend.url") or redis_url(CONFIG.get("redis.db")),
-    "broker_transport_options": CONFIG.get_dict_from_b64_json("broker.transport_options"),
+    "broker_transport_options": CONFIG.get_dict_from_b64_json(
+        "broker.transport_options", {"retry_policy": {"timeout": 5.0}}
+    ),
+    "result_backend_transport_options": CONFIG.get_dict_from_b64_json(
+        "result_backend.transport_options", {"retry_policy": {"timeout": 5.0}}
+    ),
+    "redis_retry_on_timeout": True,
 }
 
 # Sentry integration
diff --git a/authentik/tenants/scheduler.py b/authentik/tenants/scheduler.py
--- a/authentik/tenants/scheduler.py
+++ b/authentik/tenants/scheduler.py
@@ -3,6 +3,7 @@
 from tenant_schemas_celery.scheduler import (
     TenantAwarePersistentScheduler as BaseTenantAwarePersistentScheduler,
 )
+from tenant_schemas_celery.scheduler import TenantAwareScheduleEntry
 
 
 class TenantAwarePersistentScheduler(BaseTenantAwarePersistentScheduler):
@@ -11,3 +12,11 @@ class TenantAwarePersistentScheduler(BaseTenantAwarePersistentScheduler):
     @classmethod
     def get_queryset(cls):
         return super().get_queryset().filter(ready=True)
+
+    def apply_entry(self, entry: TenantAwareScheduleEntry, producer=None):
+        # https://github.com/maciej-gol/tenant-schemas-celery/blob/master/tenant_schemas_celery/scheduler.py#L85
+        # When (as by default) no tenant schemas are set, the public schema is excluded
+        # so we need to explicitly include it here, otherwise the task is not executed
+        if entry.tenant_schemas is None:
+            entry.tenant_schemas = self.get_queryset().values_list("schema_name", flat=True)
+        return super().apply_entry(entry, producer)


Test patch: