diff options
author | David Timber <dxdt@dev.snart.me> | 2022-05-20 12:16:44 +0800 |
---|---|---|
committer | David Timber <dxdt@dev.snart.me> | 2022-05-20 12:29:26 +0800 |
commit | 60a55ffab24b088b49200b178a2e7d6002abe70f (patch) | |
tree | 15013a5979c219bedf6d0ef41c328859457da68b /src/palhm/__init__.py | |
parent | c660b8c97fdb64f22cd893e1acdb6f31e816f28f (diff) |
Refactor, add AWS SNS MUA ...
- Refactor BootReport
- Modularise MUA impls
- Refactor setup_conf()
- Add missing info from __str__() funcs
- Doc AWS SNS MUA
Diffstat (limited to 'src/palhm/__init__.py')
-rw-r--r-- | src/palhm/__init__.py | 206 |
1 files changed, 137 insertions, 69 deletions
diff --git a/src/palhm/__init__.py b/src/palhm/__init__.py index 50f92ca..79a6d3f 100644 --- a/src/palhm/__init__.py +++ b/src/palhm/__init__.py @@ -22,7 +22,6 @@ import sys import time from .exceptions import InvalidConfigError -import io import json import logging import os @@ -72,44 +71,60 @@ class ValidObject (ABC): class GlobalContext: def __init__ (self, jobj: dict): - self.nb_workers = jobj.get("nb-workers", 0) + def chk_conflict (a: dict, b: dict, msg: str): + comm = set(a.keys()).intersection(b.keys()) + if comm: + raise InvalidConfigError(msg, comm) + + self.modules = {} + self.backup_backends = dict[str, BackupBackend]({ + "null": NullBackupBackend, + "localfs": LocalfsBackupBackend + }) + self.muas = dict[str, MUA]({ + "mailx": MailxMUA, + "stdout": StdoutMUA + }) + + for m in jobj.get("modules", iter(())): + loaded = self.modules[m] = import_module("." + m, "palhm.mod") + + if hasattr(loaded, "backup_backends"): + chk_conflict( + self.backup_backends, + loaded.backup_backends, + "Backup Backend conflict detected") + self.backup_backends |= loaded.backup_backends + if hasattr(loaded, "muas"): + chk_conflict( + self.muas, + loaded.muas, + "MUA conflict detected") + self.muas |= loaded.muas + + self.nb_workers = jobj.get("nb-workers", DEFAULT.NB_WORKERS.value) + if self.nb_workers < 0: + self.nb_workers = None if "vl" in jobj: self.vl = trans_vl(jobj["vl"]) else: self.vl = DEFAULT.VL.value - self.modules = {} - self.backup_backends = { - "null": NullBackupBackend, - "localfs": LocalfsBackupBackend - } self.exec_map = {} self.task_map = {} self.l = logging.getLogger("palhm") self.l.setLevel(self.vl) - self.boot_report = ( - BootReport(jobj["boot-report"]) if "boot-report" in jobj - else None) - - if self.nb_workers == 0: - self.nb_workers = DEFAULT.NB_WORKERS.value - elif self.nb_workers < 0: - self.nb_workers = None + for i in jobj.get("execs", iter(())): + self.exec_map[i["id"]] = Exec(i) + for i in jobj.get("tasks", iter(())): + self.task_map[i["id"]] = TaskClassMap[i["type"]](self, i) self.child_io_size = 4096 - for m in jobj.get("modules", iter(())): - loaded = self.modules[m] = import_module("." + m, "palhm.mod") + self.boot_report = ( + BootReport(self, jobj["boot-report"]) if "boot-report" in jobj + else None) - if hasattr(loaded, "backup_backends"): - intersect = ( - set(self.backup_backends.keys()) - .intersection(loaded.backup_backends.keys())) - if intersect: - raise InvalidConfigError( - "Backup Backend conflict detected.", - intersect) - self.backup_backends |= loaded.backup_backends def get_vl (self) -> int: return self.vl @@ -129,8 +144,10 @@ class GlobalContext: "vl: " + str(self.vl), "modules: " + " ".join([ i for i in self.modules ]), "backup_backends: " + " ".join([ i for i in self.backup_backends.keys() ]), + "muas: " + " ".join([ i for i in self.muas.keys() ]), ("exec_map:\n" + "\n".join([ i[0] + ": " + str(i[1]) for i in self.exec_map.items() ])).replace("\n", "\n\t"), - ("task_map:\n" + "\n".join([ (i[0] + ":\n" + str(i[1])).replace("\n", "\n\t") for i in self.task_map.items() ])).replace("\n", "\n\t") + ("task_map:\n" + "\n".join([ (i[0] + ":\n" + str(i[1])).replace("\n", "\n\t") for i in self.task_map.items() ])).replace("\n", "\n\t"), + "boot-report:\n\t" + (str(self.boot_report).replace("\n", "\n\t") if self.boot_report else "") ]).replace("\t", " ") class BootReport: @@ -158,14 +175,10 @@ class BootReport: "This is a boot report from {hostname}.\n" + "More details as follows.") - def __init__ (self, jobj: dict): + def __init__ (self, ctx: GlobalContext, jobj: dict): self.yaml = import_module("yaml") - mua = jobj["mua"] - if mua == "mailx": self._mua_f = self._do_send_mailx - elif mua == "stdout": self._mua_f = self._do_send_stdout - else: raise InvalidConfigError("Unsupported MUA", mua) - + self.mua = ctx.muas[jobj["mua"]](jobj.get("mua-param", {})) self.recipients = jobj["mail-to"] self.subject = jobj.get("subject", BootReport._default_subject()) self.header = jobj.get("header", BootReport._default_header()) @@ -215,33 +228,27 @@ class BootReport: yield self.yaml.dump(root_doc) def do_send (self, ctx: GlobalContext) -> int: - return self._mua_f(ctx) - - def _do_send_mailx (self, ctx: GlobalContext) -> int: - argv = [ "/bin/mailx", "-s", self.get_subject() ] + self.recipients - - with subprocess.Popen( - argv, - stdin = subprocess.PIPE, - stdout = subprocess.DEVNULL, - stderr = subprocess.PIPE) as p: - for d in self.compose_body(ctx): - p.stdin.write(d.encode()) - p.stdin.close() - - return p.wait() - - def _do_send_stdout (self, ctx: GlobalContext) -> int: - sys.stdout.write(self.get_subject() + "\n") + return self.mua.do_send( + ctx = ctx, + recipients = self.recipients, + subject = self.get_subject(), + composer = self.compose_body(ctx)) - for r in self.recipients: - sys.stdout.write(r + "\n") - sys.stdout.write("\n") - - for d in self.compose_body(ctx): - sys.stdout.write(d) - - return 0 + def __str__ (self) -> str: + return '''mua: {mua} +recipients: {recipients} +subject: {subject} +header: {header} +uptime_since: {uptime_since} +uptime: {uptime} +bootid: {bootid}'''.format( + mua = str(self.mua).replace("\n", "\n\t"), + recipients = "".join([ "\n\t- " + repr(i) for i in self.recipients]), + subject = repr(self.subject), + header = repr(self.header), + uptime_since = self.uptime_since, + uptime = self.uptime, + bootid = self.bootid) class Runnable (ABC): @abstractmethod @@ -534,7 +541,17 @@ class LocalfsBackupBackend (BackupBackend): return super()._do_fs_rotate(ctx) def __str__ (self): - return "localfs" + return '''localfs: + root: {root} + nb_copy_limit: {nb_copy_limit} + root_size_limit: {root_size_limit} + dmode: {dmode:o} + fmode: {fmode:o}'''.format( + root = self.root, + nb_copy_limit = self.nb_copy_limit, + root_size_limit = self.root_size_limit, + dmode = self.dmode, + fmode = self.fmode) def du (path: str) -> int: ret = 0 @@ -555,6 +572,65 @@ class LocalfsBackupBackend (BackupBackend): return ret +class MUA (ABC): + @abstractmethod + def do_send ( + self, + ctx: GlobalContext, + recipients: Iterable[str], + subject: str, + composer: Iterable[str]) -> int: ... + +class MailxMUA (MUA): + def __init__ (self, jobj: dict): + self.exec = jobj.get("exec", "/bin/mailx") + + def __str__ (self) -> str: + return '''mailx: + exec: {exec}'''.format(exec = self.exec) + + def do_send ( + self, + ctx: GlobalContext, + recipients: Iterable[str], + subject: str, + composer: Iterable[str]) -> int: + argv = [ self.exec, "-s", subject ] + recipients + + with subprocess.Popen( + argv, + stdin = subprocess.PIPE, + stdout = subprocess.DEVNULL, + stderr = subprocess.PIPE) as p: + for d in composer: + p.stdin.write(d.encode()) + p.stdin.close() + + return p.wait() + +class StdoutMUA (MUA): + def __init__ (self, jobj: dict): pass + + def __str__ (self) -> str: + return "stdout" + + def do_send ( + self, + ctx: GlobalContext, + recipients: Iterable[str], + subject: str, + composer: Iterable[str]) -> int: + sys.stdout.write(subject + "\n") + + for r in recipients: + sys.stdout.write(r + "\n") + sys.stdout.write("\n") + + for d in composer: + sys.stdout.write(d) + + return 0 + class BuiltinRunnable (Runnable, ValidObject): def __init__ (self): self.param = {} @@ -908,12 +984,4 @@ def load_conf (path: str, inc_set: set = set()) -> dict: return jobj def setup_conf (jobj: dict) -> GlobalContext: - ret = GlobalContext(jobj) - - for i in jobj.get("execs", iter(())): - ret.exec_map[i["id"]] = Exec(i) - - for i in jobj.get("tasks", iter(())): - ret.task_map[i["id"]] = TaskClassMap[i["type"]](ret, i) - - return ret + return GlobalContext(jobj) |