Skip to content

Commit

Permalink
sse: add the 'started_by' info to heartbeat events and support detect…
Browse files Browse the repository at this point in the history
…ing that operation were started by crons
  • Loading branch information
alexAubin committed Feb 26, 2025
1 parent afe9128 commit 9f7889c
Show file tree
Hide file tree
Showing 3 changed files with 38 additions and 12 deletions.
5 changes: 4 additions & 1 deletion locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@
"ask_new_path": "New path",
"ask_password": "Password",
"ask_user_domain": "Domain to use for the user's email address",
"automatic_task": "Automatic task",
"backup_abstract_method": "This backup method has yet to be implemented",
"backup_actually_backuping": "Creating a backup archive from the collected files…",
"backup_app_failed": "Could not back up {app}",
Expand Down Expand Up @@ -683,6 +684,7 @@
"migrations_success_forward": "Migration {id} completed",
"migrations_to_be_ran_manually": "Migration {id} has to be run manually. Please go to Tools → Migrations on the webadmin page, or run `yunohost tools migrations run`.",
"nftables_unavailable": "You cannot play with nftables here. You are either in a container or your kernel does not support it",
"noninteractive_task": "Non-interactive task",
"not_enough_disk_space": "Not enough free space on '{path}'",
"operation_interrupted": "The operation was manually interrupted?",
"other_available_options": "… and {n} other available options not shown",
Expand Down Expand Up @@ -860,8 +862,9 @@
"user_updated": "User info changed",
"visitors": "Visitors",
"yunohost_already_installed": "YunoHost is already installed",
"yunohost_api": "YunoHost API",
"yunohost_configured": "YunoHost is now configured",
"yunohost_installing": "Installing YunoHost…",
"yunohost_not_installed": "YunoHost is not correctly installed. Please run 'yunohost tools postinstall'",
"yunohost_postinstall_end_tip": "The post-install completed! To finalize your setup, please consider:\n - diagnose potential issues through the 'Diagnosis' section of the webadmin (or 'yunohost diagnosis run' in command-line);\n - reading the 'Finalizing your setup' and 'Getting to know YunoHost' parts in the admin documentation: https://yunohost.org/admindoc."
}
}
29 changes: 23 additions & 6 deletions src/log.py
Original file line number Diff line number Diff line change
Expand Up @@ -584,7 +584,6 @@ def identify_data_to_redact(self, record):
"Failed to parse line to try to identify data to redact ... : %s" % e
)


class OperationLogger:
"""
Instances of this class represents unit operation done on the ynh instance.
Expand Down Expand Up @@ -631,12 +630,8 @@ def __init__(
except Exception:
# During postinstall, we're not actually authenticated so eeeh what happens exactly?
self.started_by = "root"
elif "SUDO_USER" in os.environ:
self.started_by = os.environ["SUDO_USER"]
elif not os.isatty(1):
self.started_by = "noninteractive"
else:
self.started_by = "root"
self.started_by = _guess_who_started_process(psutil.Process())

if not os.path.exists(OPERATIONS_PATH):
os.makedirs(OPERATIONS_PATH)
Expand Down Expand Up @@ -1002,3 +997,25 @@ def _get_description_from_name(name):
@is_unit_operation(flash=True)
def log_share(path):
return log_show(path, share=True)


def _guess_who_started_process(process):

if 'SUDO_USER' in process.environ():
return process.environ()['SUDO_USER']

parent = process.parent()
parent_cli = parent.cmdline()
pparent = parent.parent() if parent else None
pparent_cli = pparent.cmdline() if pparent else []
ppparent = pparent.parent() if pparent else None
ppparent_cli = ppparent.cmdline() if ppparent else []

if any("/usr/sbin/CRON" in cli for cli in [parent_cli, pparent_cli, ppparent_cli]):
return m18n.n("automatic_task")
elif any("/usr/bin/yunohost-api" in cli for cli in [parent_cli, pparent_cli, ppparent_cli]):
return m18n.n("yunohost_api")
elif process.terminal() is None:
return m18n.n("noninteractive_task")
else:
return "root"
16 changes: 11 additions & 5 deletions src/utils/sse.py
Original file line number Diff line number Diff line change
Expand Up @@ -155,12 +155,15 @@ def close(self, *args, **kwargs):


def get_current_operation():

from yunohost.log import _guess_who_started_process

try:
with open("/var/run/moulinette_yunohost.lock") as f:
pid = f.read().strip().split("\n")[0]
lock_mtime = os.path.getmtime("/var/run/moulinette_yunohost.lock")
except FileNotFoundError:
return None, None, None
return None, None, None, None

try:
process = psutil.Process(int(pid))
Expand All @@ -169,7 +172,7 @@ def get_current_operation():
" ".join(process.cmdline()[1:]).replace("/usr/bin/", "") or "???"
)
except Exception:
return None, None, None
return None, None, None, None

active_logs = [
p.path.split("/")[-1]
Expand All @@ -183,7 +186,9 @@ def get_current_operation():
else:
operation_id = f"lock-{lock_mtime}"

return pid, operation_id, process_command_line
started_by = _guess_who_started_process(process)

return pid, operation_id, process_command_line, started_by


def sse_stream():
Expand All @@ -201,7 +206,7 @@ def sse_stream():
yield "retry: 100\n\n"

# Check if there's any ongoing operation right now
_, current_operation_id, _ = get_current_operation()
_, current_operation_id, _, _ = get_current_operation()

# Log list metadata is cached so it shouldnt be a bit deal to ask for "details" (which loads the metadata yaml for every operation)
recent_operation_history = log_list(since_days_ago=2, limit=20, with_details=True)[
Expand Down Expand Up @@ -245,11 +250,12 @@ def sse_stream():
try:
while True:
if time.time() - last_heartbeat > SSE_HEARTBEAT_PERIOD:
_, current_operation_id, cmdline = get_current_operation()
_, current_operation_id, cmdline, started_by = get_current_operation()
data = {
"current_operation": current_operation_id,
"cmdline": cmdline,
"timestamp": time.time(),
"started_by": started_by,
}
payload = json.dumps(data)
yield "event: heartbeat\n"
Expand Down

0 comments on commit 9f7889c

Please sign in to comment.