fix: harden nextcloud and wger provisioning
This commit is contained in:
parent
a2ae62bb23
commit
2777bbfc4b
@ -664,7 +664,13 @@ class ProvisioningManager:
|
|||||||
self._task_ok(conn, ctx.request_code, "nextcloud_mail_sync", None, start)
|
self._task_ok(conn, ctx.request_code, "nextcloud_mail_sync", None, start)
|
||||||
return
|
return
|
||||||
status_val = result.get("status") if isinstance(result, dict) else "error"
|
status_val = result.get("status") if isinstance(result, dict) else "error"
|
||||||
detail = str(status_val)
|
summary = result.get("summary") if isinstance(result, dict) else None
|
||||||
|
detail = ""
|
||||||
|
if summary is not None:
|
||||||
|
detail = getattr(summary, "detail", "") or ""
|
||||||
|
if not detail and isinstance(result, dict):
|
||||||
|
detail = str(result.get("detail") or "")
|
||||||
|
detail = detail or str(status_val)
|
||||||
self._task_error(conn, ctx.request_code, "nextcloud_mail_sync", detail, start)
|
self._task_error(conn, ctx.request_code, "nextcloud_mail_sync", detail, start)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
detail = safe_error_detail(exc, "failed to sync nextcloud")
|
detail = safe_error_detail(exc, "failed to sync nextcloud")
|
||||||
@ -686,7 +692,9 @@ class ProvisioningManager:
|
|||||||
result = wger.sync_user(ctx.username, ctx.mailu_email, wger_password, wait=True)
|
result = wger.sync_user(ctx.username, ctx.mailu_email, wger_password, wait=True)
|
||||||
status_val = result.get("status") if isinstance(result, dict) else "error"
|
status_val = result.get("status") if isinstance(result, dict) else "error"
|
||||||
if status_val != "ok":
|
if status_val != "ok":
|
||||||
raise RuntimeError(f"wger sync {status_val}")
|
detail = result.get("detail") if isinstance(result, dict) else ""
|
||||||
|
detail = detail or f"wger sync {status_val}"
|
||||||
|
raise RuntimeError(detail)
|
||||||
now_iso = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
|
now_iso = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
|
||||||
keycloak_admin.set_user_attribute(ctx.username, WGER_PASSWORD_UPDATED_ATTR, now_iso)
|
keycloak_admin.set_user_attribute(ctx.username, WGER_PASSWORD_UPDATED_ATTR, now_iso)
|
||||||
|
|
||||||
|
|||||||
@ -13,6 +13,7 @@ from ..k8s.exec import ExecError, PodExecutor
|
|||||||
from ..k8s.pods import PodSelectionError
|
from ..k8s.pods import PodSelectionError
|
||||||
from ..settings import settings
|
from ..settings import settings
|
||||||
from ..utils.logging import get_logger
|
from ..utils.logging import get_logger
|
||||||
|
from ..utils.passwords import random_password
|
||||||
from .keycloak_admin import keycloak_admin
|
from .keycloak_admin import keycloak_admin
|
||||||
|
|
||||||
|
|
||||||
@ -82,6 +83,7 @@ class MailSyncCounters:
|
|||||||
deleted: int = 0
|
deleted: int = 0
|
||||||
skipped: int = 0
|
skipped: int = 0
|
||||||
failures: int = 0
|
failures: int = 0
|
||||||
|
last_error: str = ""
|
||||||
|
|
||||||
def summary(self) -> NextcloudMailSyncSummary:
|
def summary(self) -> NextcloudMailSyncSummary:
|
||||||
return NextcloudMailSyncSummary(
|
return NextcloudMailSyncSummary(
|
||||||
@ -91,11 +93,17 @@ class MailSyncCounters:
|
|||||||
deleted=self.deleted,
|
deleted=self.deleted,
|
||||||
skipped=self.skipped,
|
skipped=self.skipped,
|
||||||
failures=self.failures,
|
failures=self.failures,
|
||||||
|
detail=self.last_error,
|
||||||
)
|
)
|
||||||
|
|
||||||
def status(self) -> str:
|
def status(self) -> str:
|
||||||
return "ok" if self.failures == 0 else "error"
|
return "ok" if self.failures == 0 else "error"
|
||||||
|
|
||||||
|
def record_failure(self, detail: str) -> None:
|
||||||
|
self.failures += 1
|
||||||
|
if detail and not self.last_error:
|
||||||
|
self.last_error = detail
|
||||||
|
|
||||||
|
|
||||||
class NextcloudService:
|
class NextcloudService:
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
@ -105,28 +113,88 @@ class NextcloudService:
|
|||||||
settings.nextcloud_container,
|
settings.nextcloud_container,
|
||||||
)
|
)
|
||||||
|
|
||||||
def _exec_with_fallback(self, primary: list[str], fallback: list[str]) -> ExecResult:
|
def _exec_with_fallback(
|
||||||
|
self,
|
||||||
|
primary: list[str],
|
||||||
|
fallback: list[str],
|
||||||
|
env: dict[str, str] | None = None,
|
||||||
|
check: bool = True,
|
||||||
|
) -> ExecResult:
|
||||||
try:
|
try:
|
||||||
return self._executor.exec(
|
result = self._executor.exec(
|
||||||
primary,
|
primary,
|
||||||
|
env=env,
|
||||||
timeout_sec=settings.nextcloud_exec_timeout_sec,
|
timeout_sec=settings.nextcloud_exec_timeout_sec,
|
||||||
check=True,
|
check=check,
|
||||||
)
|
)
|
||||||
except ExecError as exc:
|
except ExecError as exc:
|
||||||
if "runuser: may not be used by non-root users" not in str(exc):
|
if "runuser: may not be used by non-root users" not in str(exc):
|
||||||
raise
|
raise
|
||||||
return self._executor.exec(
|
return self._executor.exec(
|
||||||
fallback,
|
fallback,
|
||||||
|
env=env,
|
||||||
timeout_sec=settings.nextcloud_exec_timeout_sec,
|
timeout_sec=settings.nextcloud_exec_timeout_sec,
|
||||||
check=True,
|
check=check,
|
||||||
)
|
)
|
||||||
|
|
||||||
def _occ(self, args: list[str]) -> str:
|
if not result.ok and "runuser: may not be used by non-root users" in result.stderr:
|
||||||
|
return self._executor.exec(
|
||||||
|
fallback,
|
||||||
|
env=env,
|
||||||
|
timeout_sec=settings.nextcloud_exec_timeout_sec,
|
||||||
|
check=check,
|
||||||
|
)
|
||||||
|
return result
|
||||||
|
|
||||||
|
def _occ_exec(
|
||||||
|
self,
|
||||||
|
args: list[str],
|
||||||
|
env: dict[str, str] | None = None,
|
||||||
|
check: bool = True,
|
||||||
|
) -> ExecResult:
|
||||||
command = ["runuser", "-u", "www-data", "--", "php", "/var/www/html/occ", *args]
|
command = ["runuser", "-u", "www-data", "--", "php", "/var/www/html/occ", *args]
|
||||||
fallback = ["php", "/var/www/html/occ", *args]
|
fallback = ["php", "/var/www/html/occ", *args]
|
||||||
result = self._exec_with_fallback(command, fallback)
|
return self._exec_with_fallback(command, fallback, env=env, check=check)
|
||||||
|
|
||||||
|
def _occ(self, args: list[str]) -> str:
|
||||||
|
result = self._occ_exec(args, check=True)
|
||||||
return result.stdout
|
return result.stdout
|
||||||
|
|
||||||
|
def _display_name(self, user: dict[str, Any]) -> str:
|
||||||
|
first = user.get("firstName") if isinstance(user.get("firstName"), str) else ""
|
||||||
|
last = user.get("lastName") if isinstance(user.get("lastName"), str) else ""
|
||||||
|
first = first.strip()
|
||||||
|
last = last.strip()
|
||||||
|
if first and last:
|
||||||
|
return f"{first} {last}"
|
||||||
|
return last or first
|
||||||
|
|
||||||
|
def _ensure_nextcloud_user(
|
||||||
|
self,
|
||||||
|
username: str,
|
||||||
|
mailu_email: str,
|
||||||
|
display_name: str,
|
||||||
|
) -> None:
|
||||||
|
result = self._occ_exec(["user:info", username], check=False)
|
||||||
|
if result.ok:
|
||||||
|
return
|
||||||
|
|
||||||
|
detail = f"{result.stdout}\n{result.stderr}".strip().lower()
|
||||||
|
missing_markers = ("not found", "not exist", "does not exist", "unknown user")
|
||||||
|
if detail and not any(marker in detail for marker in missing_markers):
|
||||||
|
raise RuntimeError(f"nextcloud user lookup failed: {detail[:200]}")
|
||||||
|
|
||||||
|
password = random_password(24)
|
||||||
|
env = {"OC_PASS": password}
|
||||||
|
args = ["user:add", "--password-from-env"]
|
||||||
|
name = display_name or username
|
||||||
|
if name:
|
||||||
|
args += ["--display-name", name]
|
||||||
|
if mailu_email:
|
||||||
|
args += ["--email", mailu_email]
|
||||||
|
args.append(username)
|
||||||
|
self._occ_exec(args, env=env, check=True)
|
||||||
|
|
||||||
def run_cron(self) -> dict[str, Any]:
|
def run_cron(self) -> dict[str, Any]:
|
||||||
if not settings.nextcloud_namespace:
|
if not settings.nextcloud_namespace:
|
||||||
raise RuntimeError("nextcloud cron not configured")
|
raise RuntimeError("nextcloud cron not configured")
|
||||||
@ -219,10 +287,11 @@ class NextcloudService:
|
|||||||
try:
|
try:
|
||||||
return self._list_mail_accounts(username)
|
return self._list_mail_accounts(username)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
counters.failures += 1
|
detail = f"mail export failed: {exc}"
|
||||||
|
counters.record_failure(detail)
|
||||||
logger.info(
|
logger.info(
|
||||||
"nextcloud mail export failed",
|
"nextcloud mail export failed",
|
||||||
extra={"event": "nextcloud_mail_export", "status": "error", "detail": str(exc)},
|
extra={"event": "nextcloud_mail_export", "status": "error", "detail": detail},
|
||||||
)
|
)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@ -249,7 +318,7 @@ class NextcloudService:
|
|||||||
primary_id: str,
|
primary_id: str,
|
||||||
mailu_email: str,
|
mailu_email: str,
|
||||||
app_pw: str,
|
app_pw: str,
|
||||||
) -> bool:
|
) -> str | None:
|
||||||
try:
|
try:
|
||||||
self._occ(
|
self._occ(
|
||||||
[
|
[
|
||||||
@ -284,11 +353,11 @@ class NextcloudService:
|
|||||||
"password",
|
"password",
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
return True
|
return None
|
||||||
except Exception:
|
except Exception as exc:
|
||||||
return False
|
return str(exc)
|
||||||
|
|
||||||
def _create_mail_account(self, username: str, mailu_email: str, app_pw: str) -> bool:
|
def _create_mail_account(self, username: str, mailu_email: str, app_pw: str) -> str | None:
|
||||||
try:
|
try:
|
||||||
self._occ(
|
self._occ(
|
||||||
[
|
[
|
||||||
@ -310,9 +379,9 @@ class NextcloudService:
|
|||||||
"password",
|
"password",
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
return True
|
return None
|
||||||
except Exception:
|
except Exception as exc:
|
||||||
return False
|
return str(exc)
|
||||||
|
|
||||||
def _delete_extra_accounts(
|
def _delete_extra_accounts(
|
||||||
self,
|
self,
|
||||||
@ -327,8 +396,8 @@ class NextcloudService:
|
|||||||
try:
|
try:
|
||||||
self._occ(["mail:account:delete", "-q", account_id])
|
self._occ(["mail:account:delete", "-q", account_id])
|
||||||
deleted += 1
|
deleted += 1
|
||||||
except Exception:
|
except Exception as exc:
|
||||||
counters.failures += 1
|
counters.record_failure(f"mail account delete failed: {exc}")
|
||||||
return deleted
|
return deleted
|
||||||
|
|
||||||
def _mailu_accounts(self, accounts: list[tuple[str, str]]) -> list[tuple[str, str]]:
|
def _mailu_accounts(self, accounts: list[tuple[str, str]]) -> list[tuple[str, str]]:
|
||||||
@ -360,7 +429,7 @@ class NextcloudService:
|
|||||||
self,
|
self,
|
||||||
user: dict[str, Any],
|
user: dict[str, Any],
|
||||||
counters: MailSyncCounters,
|
counters: MailSyncCounters,
|
||||||
) -> tuple[str, str, str, str] | None:
|
) -> tuple[str, str, str, str, dict[str, Any]] | None:
|
||||||
normalized = self._normalize_user(user)
|
normalized = self._normalize_user(user)
|
||||||
if not normalized:
|
if not normalized:
|
||||||
counters.skipped += 1
|
counters.skipped += 1
|
||||||
@ -377,7 +446,7 @@ class NextcloudService:
|
|||||||
keycloak_admin.set_user_attribute(username, "mailu_email", mailu_email)
|
keycloak_admin.set_user_attribute(username, "mailu_email", mailu_email)
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
return username, user_id, mailu_email, app_pw
|
return username, user_id, mailu_email, app_pw, full_user
|
||||||
|
|
||||||
def _sync_mail_accounts(
|
def _sync_mail_accounts(
|
||||||
self,
|
self,
|
||||||
@ -390,14 +459,16 @@ class NextcloudService:
|
|||||||
mailu_accounts = self._mailu_accounts(accounts)
|
mailu_accounts = self._mailu_accounts(accounts)
|
||||||
if mailu_accounts:
|
if mailu_accounts:
|
||||||
primary_id, _primary_email = self._select_primary_account(mailu_accounts, mailu_email)
|
primary_id, _primary_email = self._select_primary_account(mailu_accounts, mailu_email)
|
||||||
if not self._update_mail_account(username, primary_id, mailu_email, app_pw):
|
error = self._update_mail_account(username, primary_id, mailu_email, app_pw)
|
||||||
counters.failures += 1
|
if error:
|
||||||
|
counters.record_failure(f"mail account update failed: {error}")
|
||||||
return False
|
return False
|
||||||
counters.updated += 1
|
counters.updated += 1
|
||||||
counters.deleted += self._delete_extra_accounts(mailu_accounts, primary_id, counters)
|
counters.deleted += self._delete_extra_accounts(mailu_accounts, primary_id, counters)
|
||||||
else:
|
else:
|
||||||
if not self._create_mail_account(username, mailu_email, app_pw):
|
error = self._create_mail_account(username, mailu_email, app_pw)
|
||||||
counters.failures += 1
|
if error:
|
||||||
|
counters.record_failure(f"mail account create failed: {error}")
|
||||||
return False
|
return False
|
||||||
counters.created += 1
|
counters.created += 1
|
||||||
return True
|
return True
|
||||||
@ -417,7 +488,14 @@ class NextcloudService:
|
|||||||
context = self._mail_sync_context(user, counters)
|
context = self._mail_sync_context(user, counters)
|
||||||
if not context:
|
if not context:
|
||||||
return
|
return
|
||||||
username, user_id, mailu_email, app_pw = context
|
username, user_id, mailu_email, app_pw, full_user = context
|
||||||
|
|
||||||
|
try:
|
||||||
|
display_name = self._display_name(full_user)
|
||||||
|
self._ensure_nextcloud_user(username, mailu_email, display_name)
|
||||||
|
except Exception as exc:
|
||||||
|
counters.record_failure(f"nextcloud user ensure failed: {exc}")
|
||||||
|
return
|
||||||
|
|
||||||
accounts = self._list_mail_accounts_safe(username, counters)
|
accounts = self._list_mail_accounts_safe(username, counters)
|
||||||
if accounts is None:
|
if accounts is None:
|
||||||
@ -465,10 +543,11 @@ class NextcloudService:
|
|||||||
"deleted_count": counters.deleted,
|
"deleted_count": counters.deleted,
|
||||||
"skipped_count": counters.skipped,
|
"skipped_count": counters.skipped,
|
||||||
"failures_count": counters.failures,
|
"failures_count": counters.failures,
|
||||||
|
"detail": summary.detail,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
return {"status": counters.status(), "summary": summary}
|
return {"status": counters.status(), "summary": summary, "detail": summary.detail}
|
||||||
|
|
||||||
def _run_shell(self, script: str, check: bool = True) -> None:
|
def _run_shell(self, script: str, check: bool = True) -> None:
|
||||||
self._executor.exec(
|
self._executor.exec(
|
||||||
|
|||||||
@ -144,7 +144,8 @@ _WGER_SYNC_SCRIPT = textwrap.dedent(
|
|||||||
|
|
||||||
|
|
||||||
def _wger_exec_command() -> str:
|
def _wger_exec_command() -> str:
|
||||||
return f"python3 - <<'PY'\n{_WGER_SYNC_SCRIPT}\nPY"
|
bootstrap = "if [ -f /vault/secrets/wger-env ]; then . /vault/secrets/wger-env; fi"
|
||||||
|
return f"{bootstrap}\npython3 - <<'PY'\n{_WGER_SYNC_SCRIPT}\nPY"
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
@ -419,7 +420,9 @@ class WgerService:
|
|||||||
result = self.sync_user(prepared.username, prepared.mailu_email, prepared.password, wait=True)
|
result = self.sync_user(prepared.username, prepared.mailu_email, prepared.password, wait=True)
|
||||||
result_status = result.get("status") if isinstance(result, dict) else "error"
|
result_status = result.get("status") if isinstance(result, dict) else "error"
|
||||||
if result_status != "ok":
|
if result_status != "ok":
|
||||||
return UserSyncOutcome("failed", f"sync {result_status}")
|
detail = result.get("detail") if isinstance(result, dict) else ""
|
||||||
|
detail = detail or f"sync {result_status}"
|
||||||
|
return UserSyncOutcome("failed", detail)
|
||||||
if not _set_wger_updated_at(prepared.username):
|
if not _set_wger_updated_at(prepared.username):
|
||||||
return UserSyncOutcome("failed", "failed to set updated_at")
|
return UserSyncOutcome("failed", "failed to set updated_at")
|
||||||
|
|
||||||
|
|||||||
@ -2,6 +2,8 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import types
|
import types
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
from ariadne.k8s.exec import ExecError
|
from ariadne.k8s.exec import ExecError
|
||||||
from ariadne.services import nextcloud as nextcloud_module
|
from ariadne.services import nextcloud as nextcloud_module
|
||||||
from ariadne.services.nextcloud import NextcloudService, _parse_mail_export
|
from ariadne.services.nextcloud import NextcloudService, _parse_mail_export
|
||||||
@ -62,6 +64,7 @@ def test_nextcloud_sync_mail_create(monkeypatch) -> None:
|
|||||||
|
|
||||||
svc = NextcloudService()
|
svc = NextcloudService()
|
||||||
monkeypatch.setattr(svc, "_occ", fake_occ)
|
monkeypatch.setattr(svc, "_occ", fake_occ)
|
||||||
|
monkeypatch.setattr(svc, "_ensure_nextcloud_user", lambda *_args, **_kwargs: None)
|
||||||
monkeypatch.setattr(svc, "_list_mail_accounts", lambda *_args, **_kwargs: list_calls.pop(0))
|
monkeypatch.setattr(svc, "_list_mail_accounts", lambda *_args, **_kwargs: list_calls.pop(0))
|
||||||
monkeypatch.setattr(svc, "_set_editor_mode_richtext", lambda *_args, **_kwargs: None)
|
monkeypatch.setattr(svc, "_set_editor_mode_richtext", lambda *_args, **_kwargs: None)
|
||||||
monkeypatch.setattr(svc, "_set_user_mail_meta", lambda *_args, **_kwargs: None)
|
monkeypatch.setattr(svc, "_set_user_mail_meta", lambda *_args, **_kwargs: None)
|
||||||
@ -142,6 +145,7 @@ def test_nextcloud_sync_mail_update_and_delete(monkeypatch) -> None:
|
|||||||
|
|
||||||
svc = NextcloudService()
|
svc = NextcloudService()
|
||||||
monkeypatch.setattr(svc, "_occ", fake_occ)
|
monkeypatch.setattr(svc, "_occ", fake_occ)
|
||||||
|
monkeypatch.setattr(svc, "_ensure_nextcloud_user", lambda *_args, **_kwargs: None)
|
||||||
monkeypatch.setattr(svc, "_list_mail_accounts", lambda *_args, **_kwargs: list_calls.pop(0))
|
monkeypatch.setattr(svc, "_list_mail_accounts", lambda *_args, **_kwargs: list_calls.pop(0))
|
||||||
monkeypatch.setattr(svc, "_set_editor_mode_richtext", lambda *_args, **_kwargs: None)
|
monkeypatch.setattr(svc, "_set_editor_mode_richtext", lambda *_args, **_kwargs: None)
|
||||||
monkeypatch.setattr(svc, "_set_user_mail_meta", lambda *_args, **_kwargs: None)
|
monkeypatch.setattr(svc, "_set_user_mail_meta", lambda *_args, **_kwargs: None)
|
||||||
@ -227,6 +231,72 @@ def test_nextcloud_occ_fallback(monkeypatch) -> None:
|
|||||||
assert executor.calls[1][0] == "php"
|
assert executor.calls[1][0] == "php"
|
||||||
|
|
||||||
|
|
||||||
|
def test_nextcloud_ensure_user_created(monkeypatch) -> None:
|
||||||
|
dummy_settings = types.SimpleNamespace(
|
||||||
|
nextcloud_namespace="nextcloud",
|
||||||
|
nextcloud_mail_sync_cronjob="nextcloud-mail-sync",
|
||||||
|
nextcloud_mail_sync_wait_timeout_sec=90.0,
|
||||||
|
nextcloud_mail_sync_job_ttl_sec=3600,
|
||||||
|
nextcloud_pod_label="app=nextcloud",
|
||||||
|
nextcloud_container="nextcloud",
|
||||||
|
nextcloud_exec_timeout_sec=30.0,
|
||||||
|
nextcloud_db_host="",
|
||||||
|
nextcloud_db_port=5432,
|
||||||
|
nextcloud_db_name="nextcloud",
|
||||||
|
nextcloud_db_user="nextcloud",
|
||||||
|
nextcloud_db_password="",
|
||||||
|
mailu_domain="bstein.dev",
|
||||||
|
mailu_host="mail.bstein.dev",
|
||||||
|
)
|
||||||
|
monkeypatch.setattr(nextcloud_module, "settings", dummy_settings)
|
||||||
|
monkeypatch.setattr(nextcloud_module, "random_password", lambda *_args, **_kwargs: "pw")
|
||||||
|
|
||||||
|
svc = NextcloudService()
|
||||||
|
calls: list[tuple[list[str], dict[str, str] | None, bool]] = []
|
||||||
|
|
||||||
|
def fake_occ_exec(args, env=None, check=True):
|
||||||
|
calls.append((args, env, check))
|
||||||
|
if args[0] == "user:info":
|
||||||
|
return types.SimpleNamespace(stdout="User does not exist", stderr="", exit_code=1, ok=False)
|
||||||
|
return types.SimpleNamespace(stdout="created", stderr="", exit_code=0, ok=True)
|
||||||
|
|
||||||
|
monkeypatch.setattr(svc, "_occ_exec", fake_occ_exec)
|
||||||
|
|
||||||
|
svc._ensure_nextcloud_user("alice", "alice@bstein.dev", "Alice")
|
||||||
|
assert any(call[0][0] == "user:add" for call in calls)
|
||||||
|
assert calls[-1][1]["OC_PASS"] == "pw"
|
||||||
|
|
||||||
|
|
||||||
|
def test_nextcloud_ensure_user_lookup_error(monkeypatch) -> None:
|
||||||
|
dummy_settings = types.SimpleNamespace(
|
||||||
|
nextcloud_namespace="nextcloud",
|
||||||
|
nextcloud_mail_sync_cronjob="nextcloud-mail-sync",
|
||||||
|
nextcloud_mail_sync_wait_timeout_sec=90.0,
|
||||||
|
nextcloud_mail_sync_job_ttl_sec=3600,
|
||||||
|
nextcloud_pod_label="app=nextcloud",
|
||||||
|
nextcloud_container="nextcloud",
|
||||||
|
nextcloud_exec_timeout_sec=30.0,
|
||||||
|
nextcloud_db_host="",
|
||||||
|
nextcloud_db_port=5432,
|
||||||
|
nextcloud_db_name="nextcloud",
|
||||||
|
nextcloud_db_user="nextcloud",
|
||||||
|
nextcloud_db_password="",
|
||||||
|
mailu_domain="bstein.dev",
|
||||||
|
mailu_host="mail.bstein.dev",
|
||||||
|
)
|
||||||
|
monkeypatch.setattr(nextcloud_module, "settings", dummy_settings)
|
||||||
|
|
||||||
|
svc = NextcloudService()
|
||||||
|
|
||||||
|
def fake_occ_exec(args, env=None, check=True):
|
||||||
|
return types.SimpleNamespace(stdout="permission denied", stderr="", exit_code=1, ok=False)
|
||||||
|
|
||||||
|
monkeypatch.setattr(svc, "_occ_exec", fake_occ_exec)
|
||||||
|
|
||||||
|
with pytest.raises(RuntimeError):
|
||||||
|
svc._ensure_nextcloud_user("alice", "alice@bstein.dev", "Alice")
|
||||||
|
|
||||||
|
|
||||||
def test_nextcloud_run_maintenance(monkeypatch) -> None:
|
def test_nextcloud_run_maintenance(monkeypatch) -> None:
|
||||||
dummy_settings = types.SimpleNamespace(
|
dummy_settings = types.SimpleNamespace(
|
||||||
nextcloud_namespace="nextcloud",
|
nextcloud_namespace="nextcloud",
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user