from shlex import quote from pathlib import Path from bundlewrap.items import Item S6RC_ROOT = "/etc/s6-rc" def generate_request_path(template, service) -> str: return Path(S6RC_ROOT, "generate", f"{template}.{service}").as_posix() def service_dependency_path(bundle, service) -> str: return Path(S6RC_ROOT, "services", bundle, "contents.d", service).as_posix() def have_generate_template(node, template) -> bool: template_path = Path(S6RC_ROOT, "templates", template) try: result = node.run( f"test -d {quote(template_path.as_posix())}", ) return result.return_code == 0 except: return False def is_service_generated(node, template, service) -> bool: try: result = node.run( f"test -f {quote(generate_request_path(template, service))}", ) return result.return_code == 0 except: return False def generate_service(node, template, service) -> bool: """ This doesn't really generate the service from the template. Rather it requests the s6-rc-generate target to do it on boot. """ result = node.run( f"touch {quote(generate_request_path(template, service))}", ) return result.return_code == 0 def is_service_registered(node, bundle, service) -> bool: try: result = node.run( f"test -f {quote(service_dependency_path(bundle, service))}", ) return result.return_code == 0 except: return False def register_service(node, bundle, service) -> bool: result = node.run( f"touch {quote(service_dependency_path(bundle, service))}", ) return result.return_code == 0 def is_service_running(node, service) -> bool: try: result = node.run( f"s6-rc -v2 -bt 30000 -a list | grep -q {quote(service)}", ) return result.return_code == 0 except: return False def run_service(node, service) -> bool: result = node.run( f"s6-rc -v2 -bt 30000 start {quote(service)}", ) return result.return_code == 0 class SvcS6RC(Item): """ A service managed by s6-rc. """ BUNDLE_ATTRIBUTE_NAME = "svc_s6rc" ITEM_ATTRIBUTES = { "bundle": "default", "generate": None, "running": True, } ITEM_TYPE_NAME = "svc_s6rc" REQUIRED_ATTRIBUTES = [] def __repr__(self): return "".format( self.name, self.attributes["running"], self.attributes["bundle"], self.attributes["generate"], ) def get_canned_actions(self): return { "stop": { "command": f"s6-rc -v2 -b -t 30000 stop {quote(self.name)}", "needed_by": {self.id}, }, "restart": { "command": f"s6-rc -v2 -b -t 30000 stop {quote(self.name)} && s6-rc -v2 -b -t 30000 start {quote(self.name)}", "needs": {self.id}, }, } def cdict(self): return { "running": self.attributes["running"], "generated": self.attributes["generate"] != None, "registered": self.attributes["bundle"] != None, } def sdict(self): return { "running": is_service_running(self.node, self.name), "generated": is_service_generated(self.node, self.attributes["generate"], self.name) if self.attributes["generate"] != None else False, "registered": is_service_registered(self.node,self.attributes["bundle"], self.name), } def fix(self, status): if status.correct: return if self.attributes["generate"]: if not have_generate_template(self.node, self.attributes["generate"]): raise Exception(f"No template provides `{self.attributes["generate"]}`") if status.cdict["generated"] != status.sdict["generated"]: generate_service(self.node, self.attributes["generate"], self.name) if status.cdict["registered"] != status.sdict["registered"]: register_service(self.node, self.attributes["bundle"], self.name) if status.cdict["running"] != status.sdict["running"]: run_service(self.node, self.name)