core-extra/daemon/core/emulator/hooks.py
2023-06-13 17:00:53 -07:00

145 lines
4.8 KiB
Python

import logging
import subprocess
from collections.abc import Callable
from pathlib import Path
from core.emulator.enumerations import EventTypes
from core.errors import CoreError
logger = logging.getLogger(__name__)
class HookManager:
"""
Provides functionality for managing and running script/callback hooks.
"""
def __init__(self) -> None:
"""
Create a HookManager instance.
"""
self.script_hooks: dict[EventTypes, dict[str, str]] = {}
self.callback_hooks: dict[EventTypes, list[Callable[[], None]]] = {}
def reset(self) -> None:
"""
Clear all current hooks.
:return: nothing
"""
self.script_hooks.clear()
self.callback_hooks.clear()
def add_script_hook(self, state: EventTypes, file_name: str, data: str) -> None:
"""
Add a hook script to run for a given state.
:param state: state to run hook on
:param file_name: hook file name
:param data: file data
:return: nothing
"""
logger.info("setting state hook: %s - %s", state, file_name)
state_hooks = self.script_hooks.setdefault(state, {})
if file_name in state_hooks:
raise CoreError(
f"adding duplicate state({state.name}) hook script({file_name})"
)
state_hooks[file_name] = data
def delete_script_hook(self, state: EventTypes, file_name: str) -> None:
"""
Delete a script hook from a given state.
:param state: state to delete script hook from
:param file_name: name of script to delete
:return: nothing
"""
state_hooks = self.script_hooks.get(state, {})
if file_name not in state_hooks:
raise CoreError(
f"deleting state({state.name}) hook script({file_name}) "
"that does not exist"
)
del state_hooks[file_name]
def add_callback_hook(
self, state: EventTypes, hook: Callable[[EventTypes], None]
) -> None:
"""
Add a hook callback to run for a state.
:param state: state to add hook for
:param hook: callback to run
:return: nothing
"""
hooks = self.callback_hooks.setdefault(state, [])
if hook in hooks:
name = getattr(callable, "__name__", repr(hook))
raise CoreError(
f"adding duplicate state({state.name}) hook callback({name})"
)
hooks.append(hook)
def delete_callback_hook(
self, state: EventTypes, hook: Callable[[EventTypes], None]
) -> None:
"""
Delete a state hook.
:param state: state to delete hook for
:param hook: hook to delete
:return: nothing
"""
hooks = self.callback_hooks.get(state, [])
if hook not in hooks:
name = getattr(callable, "__name__", repr(hook))
raise CoreError(
f"deleting state({state.name}) hook callback({name}) "
"that does not exist"
)
hooks.remove(hook)
def run_hooks(
self, state: EventTypes, directory: Path, env: dict[str, str]
) -> None:
"""
Run all hooks for the current state.
:param state: state to run hooks for
:param directory: directory to run script hooks within
:param env: environment to run script hooks with
:return: nothing
"""
for state_hooks in self.script_hooks.get(state, {}):
for file_name, data in state_hooks.items():
logger.info("running hook %s", file_name)
file_path = directory / file_name
log_path = directory / f"{file_name}.log"
try:
with file_path.open("w") as f:
f.write(data)
with log_path.open("w") as f:
args = ["/bin/sh", file_name]
subprocess.check_call(
args,
stdout=f,
stderr=subprocess.STDOUT,
close_fds=True,
cwd=directory,
env=env,
)
except (OSError, subprocess.CalledProcessError) as e:
raise CoreError(
f"failure running state({state.name}) "
f"hook script({file_name}): {e}"
)
for hook in self.callback_hooks.get(state, []):
try:
hook()
except Exception as e:
name = getattr(callable, "__name__", repr(hook))
raise CoreError(
f"failure running state({state.name}) "
f"hook callback({name}): {e}"
)