fix(observer): make world/resolve-requests/ group-writable
world/resolve-requests/ was created via plain mkdir (0o755, masked by umask), so the SSH operator (group aerbot) could not drop a resolve flag there — the manual incident-resolve path required docker exec, defeating its "operator drops a file" design. Same defect and fix as executor.py's INBOX_DIR_MODE (2026-08-06): an explicit, idempotent os.chmod(0o775) after mkdir, applied at the same site the directory is created/used. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VMn76yx2CNuHKFVMrYcKWA
This commit is contained in:
parent
419df70e29
commit
89f75c34b0
|
|
@ -141,6 +141,11 @@ FAILED_EVENTS_DIR = STATE_DIR / "observer_failed_events"
|
||||||
# "operator drops a file, the owning process consumes it" pattern the actions
|
# "operator drops a file, the owning process consumes it" pattern the actions
|
||||||
# pending/approved queue already uses.
|
# pending/approved queue already uses.
|
||||||
RESOLVE_REQUESTS_DIR = WORLD_DIR / "resolve-requests"
|
RESOLVE_REQUESTS_DIR = WORLD_DIR / "resolve-requests"
|
||||||
|
# mkdir(mode=...) is masked by the process umask, so the SSH operator (group
|
||||||
|
# aerbot) cannot drop a flag file into a dir created at the default 0o755 —
|
||||||
|
# same defect/fix as executor.py's INBOX_DIR_MODE (2026-08-06): an explicit,
|
||||||
|
# idempotent os.chmod after mkdir, applied on the same path the dir is used.
|
||||||
|
RESOLVE_REQUESTS_DIR_MODE = 0o775
|
||||||
|
|
||||||
# Time-based fallback for incidents that can never receive the service_healthy
|
# Time-based fallback for incidents that can never receive the service_healthy
|
||||||
# event _resolve_incident() waits for (service removed/renamed/decommissioned
|
# event _resolve_incident() waits for (service removed/renamed/decommissioned
|
||||||
|
|
@ -277,6 +282,13 @@ class Observer:
|
||||||
LOGS_DIR.mkdir(parents=True, exist_ok=True)
|
LOGS_DIR.mkdir(parents=True, exist_ok=True)
|
||||||
FAILED_EVENTS_DIR.mkdir(parents=True, exist_ok=True)
|
FAILED_EVENTS_DIR.mkdir(parents=True, exist_ok=True)
|
||||||
RESOLVE_REQUESTS_DIR.mkdir(parents=True, exist_ok=True)
|
RESOLVE_REQUESTS_DIR.mkdir(parents=True, exist_ok=True)
|
||||||
|
try:
|
||||||
|
os.chmod(RESOLVE_REQUESTS_DIR, RESOLVE_REQUESTS_DIR_MODE)
|
||||||
|
except OSError as e:
|
||||||
|
logger.warning(
|
||||||
|
f"Could not set mode {oct(RESOLVE_REQUESTS_DIR_MODE)} on "
|
||||||
|
f"{RESOLVE_REQUESTS_DIR}: {e}"
|
||||||
|
)
|
||||||
|
|
||||||
def _quarantine_event_file(self, file_path: str, node_dir: str, exc: Exception) -> None:
|
def _quarantine_event_file(self, file_path: str, node_dir: str, exc: Exception) -> None:
|
||||||
"""Move an unreadable/unprocessable event out of the hot path."""
|
"""Move an unreadable/unprocessable event out of the hot path."""
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import json
|
import json
|
||||||
|
import os
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
@ -915,3 +916,19 @@ def test_resolve_request_flag_for_already_resolved_incident_is_removed(tmp_path)
|
||||||
assert obs.world_state["incidents"][inc_id]["status"] == "resolved"
|
assert obs.world_state["incidents"][inc_id]["status"] == "resolved"
|
||||||
assert obs.world_state["incidents"][inc_id]["resolved_reason"] == "manual_operator"
|
assert obs.world_state["incidents"][inc_id]["resolved_reason"] == "manual_operator"
|
||||||
assert not flag.exists()
|
assert not flag.exists()
|
||||||
|
|
||||||
|
|
||||||
|
def test_resolve_requests_dir_is_group_writable(tmp_path, monkeypatch):
|
||||||
|
"""world/resolve-requests/ must be group-writable so an SSH operator
|
||||||
|
(group aerbot, not the observer's own user) can drop a resolve flag file
|
||||||
|
without docker exec — mkdir(mode=...) alone is masked by the process
|
||||||
|
umask, same defect/fix as executor.py's INBOX_DIR_MODE (2026-08-06)."""
|
||||||
|
old_umask = os.umask(0o022)
|
||||||
|
try:
|
||||||
|
obs = _make_observer_simple(tmp_path)
|
||||||
|
finally:
|
||||||
|
os.umask(old_umask)
|
||||||
|
import observer.observer as obs_mod
|
||||||
|
|
||||||
|
mode = obs_mod.RESOLVE_REQUESTS_DIR.stat().st_mode & 0o777
|
||||||
|
assert mode == 0o775
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue