D7net
Home
Console
Upload
information
Create File
Create Folder
About
Tools
:
/
opt
/
saltstack
/
salt
/
lib
/
python3.10
/
site-packages
/
salt
/
modules
/
Filename :
runit.py
back
Copy
""" runit service module (http://smarden.org/runit) This module is compatible with the :mod:`service <salt.states.service>` states, so it can be used to maintain services using the ``provider`` argument: .. code-block:: yaml myservice: service: - running - provider: runit Provides virtual `service` module on systems using runit as init. Service management rules (`sv` command): service $n is ENABLED if file SERVICE_DIR/$n/run exists service $n is AVAILABLE if ENABLED or if file AVAIL_SVR_DIR/$n/run exists service $n is DISABLED if AVAILABLE but not ENABLED SERVICE_DIR/$n is normally a symlink to a AVAIL_SVR_DIR/$n folder Service auto-start/stop mechanism: `sv` (auto)starts/stops service as soon as SERVICE_DIR/<service> is created/deleted, both on service creation or a boot time. autostart feature is disabled if file SERVICE_DIR/<n>/down exists. This does not affect the current's service status (if already running) nor manual service management. Service's alias: Service `sva` is an alias of service `svc` when `AVAIL_SVR_DIR/sva` symlinks to folder `AVAIL_SVR_DIR/svc`. `svc` can't be enabled if it is already enabled through an alias already enabled, since `sv` files are stored in folder `SERVICE_DIR/svc/`. XBPS package management uses a service's alias to provides service alternative(s), such as chrony and openntpd both aliased to ntpd. """ import glob import logging import os import time import salt.utils.files import salt.utils.path from salt.exceptions import CommandExecutionError log = logging.getLogger(__name__) # Function alias to not shadow built-ins. __func_alias__ = {"reload_": "reload"} # which dir sv works with VALID_SERVICE_DIRS = [ "/service", "/var/service", "/etc/service", ] SERVICE_DIR = None for service_dir in VALID_SERVICE_DIRS: if os.path.exists(service_dir): SERVICE_DIR = service_dir break # available service directory(ies) AVAIL_SVR_DIRS = [] # Define the module's virtual name __virtualname__ = "runit" __virtual_aliases__ = ("runit",) def __virtual__(): """ Virtual service only on systems using runit as init process (PID 1). Otherwise, use this module with the provider mechanism. """ if __grains__.get("init") == "runit": if __grains__["os"] == "Void": add_svc_avail_path("/etc/sv") global __virtualname__ __virtualname__ = "service" return __virtualname__ if salt.utils.path.which("sv"): return __virtualname__ return (False, "Runit not available. Please install sv") def _service_path(name): """ Return SERVICE_DIR+name if possible name the service's name to work on """ if not SERVICE_DIR: raise CommandExecutionError("Could not find service directory.") return os.path.join(SERVICE_DIR, name) # -- states.service compatible args def start(name): """ Start service name the service's name CLI Example: .. code-block:: bash salt '*' runit.start <service name> """ cmd = "sv start {}".format(_service_path(name)) return not __salt__["cmd.retcode"](cmd) # -- states.service compatible args def stop(name): """ Stop service name the service's name CLI Example: .. code-block:: bash salt '*' runit.stop <service name> """ cmd = "sv stop {}".format(_service_path(name)) return not __salt__["cmd.retcode"](cmd) # -- states.service compatible def reload_(name): """ Reload service name the service's name CLI Example: .. code-block:: bash salt '*' runit.reload <service name> """ cmd = "sv reload {}".format(_service_path(name)) return not __salt__["cmd.retcode"](cmd) # -- states.service compatible def restart(name): """ Restart service name the service's name CLI Example: .. code-block:: bash salt '*' runit.restart <service name> """ cmd = "sv restart {}".format(_service_path(name)) return not __salt__["cmd.retcode"](cmd) # -- states.service compatible def full_restart(name): """ Calls runit.restart() name the service's name CLI Example: .. code-block:: bash salt '*' runit.full_restart <service name> """ restart(name) # -- states.service compatible def status(name, sig=None): """ Return ``True`` if service is running name the service's name sig signature to identify with ps CLI Example: .. code-block:: bash salt '*' runit.status <service name> """ if sig: # usual way to do by others (debian_service, netbsdservice). # XXX probably does not work here (check 'runsv sshd' instead of 'sshd' ?) return bool(__salt__["status.pid"](sig)) svc_path = _service_path(name) if not os.path.exists(svc_path): # service does not exist return False # sv return code is not relevant to get a service status. # Check its output instead. cmd = "sv status {}".format(svc_path) try: out = __salt__["cmd.run_stdout"](cmd) return out.startswith("run: ") except Exception: # pylint: disable=broad-except # sv (as a command) returned an error return False def _is_svc(svc_path): """ Return ``True`` if directory <svc_path> is really a service: file <svc_path>/run exists and is executable svc_path the (absolute) directory to check for compatibility """ run_file = os.path.join(svc_path, "run") if ( os.path.exists(svc_path) and os.path.exists(run_file) and os.access(run_file, os.X_OK) ): return True return False def status_autostart(name): """ Return ``True`` if service <name> is autostarted by sv (file $service_folder/down does not exist) NB: return ``False`` if the service is not enabled. name the service's name CLI Example: .. code-block:: bash salt '*' runit.status_autostart <service name> """ return not os.path.exists(os.path.join(_service_path(name), "down")) def get_svc_broken_path(name="*"): """ Return list of broken path(s) in SERVICE_DIR that match ``name`` A path is broken if it is a broken symlink or can not be a runit service name a glob for service name. default is '*' CLI Example: .. code-block:: bash salt '*' runit.get_svc_broken_path <service name> """ if not SERVICE_DIR: raise CommandExecutionError("Could not find service directory.") ret = set() for el in glob.glob(os.path.join(SERVICE_DIR, name)): if not _is_svc(el): ret.add(el) return sorted(ret) def get_svc_avail_path(): """ Return list of paths that may contain available services """ return AVAIL_SVR_DIRS def add_svc_avail_path(path): """ Add a path that may contain available services. Return ``True`` if added (or already present), ``False`` on error. path directory to add to AVAIL_SVR_DIRS """ if os.path.exists(path): if path not in AVAIL_SVR_DIRS: AVAIL_SVR_DIRS.append(path) return True return False def _get_svc_path(name="*", status=None): """ Return a list of paths to services with ``name`` that have the specified ``status`` name a glob for service name. default is '*' status None : all services (no filter, default choice) 'DISABLED' : available service(s) that is not enabled 'ENABLED' : enabled service (whether started on boot or not) """ # This is the core routine to work with services, called by many # other functions of this module. # # The name of a service is the "apparent" folder's name that contains its # "run" script. If its "folder" is a symlink, the service is an "alias" of # the targeted service. if not SERVICE_DIR: raise CommandExecutionError("Could not find service directory.") # path list of enabled services as /AVAIL_SVR_DIRS/$service, # taking care of any service aliases (do not use os.path.realpath()). ena = set() for el in glob.glob(os.path.join(SERVICE_DIR, name)): if _is_svc(el): if os.path.islink(el): ena.add(os.readlink(el)) else: ena.add(el) log.trace("found enabled service path: %s", el) if status == "ENABLED": return sorted(ena) # path list of available services as /AVAIL_SVR_DIRS/$service ava = set() for d in AVAIL_SVR_DIRS: for el in glob.glob(os.path.join(d, name)): if _is_svc(el): ava.add(el) log.trace("found available service path: %s", el) if status == "DISABLED": # service available but not enabled ret = ava.difference(ena) else: # default: return available services ret = ava.union(ena) return sorted(ret) def _get_svc_list(name="*", status=None): """ Return list of services that have the specified service ``status`` name a glob for service name. default is '*' status None : all services (no filter, default choice) 'DISABLED' : available service that is not enabled 'ENABLED' : enabled service (whether started on boot or not) """ return sorted(os.path.basename(el) for el in _get_svc_path(name, status)) def get_svc_alias(): """ Returns the list of service's name that are aliased and their alias path(s) """ ret = {} for d in AVAIL_SVR_DIRS: for el in glob.glob(os.path.join(d, "*")): if not os.path.islink(el): continue psvc = os.readlink(el) if not os.path.isabs(psvc): psvc = os.path.join(d, psvc) nsvc = os.path.basename(psvc) if nsvc not in ret: ret[nsvc] = [] ret[nsvc].append(el) return ret def available(name): """ Returns ``True`` if the specified service is available, otherwise returns ``False``. name the service's name CLI Example: .. code-block:: bash salt '*' runit.available <service name> """ return name in _get_svc_list(name) def missing(name): """ The inverse of runit.available. Returns ``True`` if the specified service is not available, otherwise returns ``False``. name the service's name CLI Example: .. code-block:: bash salt '*' runit.missing <service name> """ return name not in _get_svc_list(name) def get_all(): """ Return a list of all available services CLI Example: .. code-block:: bash salt '*' runit.get_all """ return _get_svc_list() def get_enabled(): """ Return a list of all enabled services CLI Example: .. code-block:: bash salt '*' service.get_enabled """ return _get_svc_list(status="ENABLED") def get_disabled(): """ Return a list of all disabled services CLI Example: .. code-block:: bash salt '*' service.get_disabled """ return _get_svc_list(status="DISABLED") def enabled(name): """ Return ``True`` if the named service is enabled, ``False`` otherwise name the service's name CLI Example: .. code-block:: bash salt '*' service.enabled <service name> """ # exhaustive check instead of (only) os.path.exists(_service_path(name)) return name in _get_svc_list(name, "ENABLED") def disabled(name): """ Return ``True`` if the named service is disabled, ``False`` otherwise name the service's name CLI Example: .. code-block:: bash salt '*' service.disabled <service name> """ # return True for a non-existent service return name not in _get_svc_list(name, "ENABLED") def show(name): """ Show properties of one or more units/jobs or the manager name the service's name CLI Example: .. code-block:: bash salt '*' service.show <service name> """ ret = {} ret["enabled"] = False ret["disabled"] = True ret["running"] = False ret["service_path"] = None ret["autostart"] = False ret["command_path"] = None ret["available"] = available(name) if not ret["available"]: return ret ret["enabled"] = enabled(name) ret["disabled"] = not ret["enabled"] ret["running"] = status(name) ret["autostart"] = status_autostart(name) ret["service_path"] = _get_svc_path(name)[0] if ret["service_path"]: ret["command_path"] = os.path.join(ret["service_path"], "run") # XXX provide info about alias ? return ret def enable(name, start=False, **kwargs): """ Start service ``name`` at boot. Returns ``True`` if operation is successful name the service's name start : False If ``True``, start the service once enabled. CLI Example: .. code-block:: bash salt '*' service.enable <name> [start=True] """ # non-existent service if not available(name): return False # if service is aliased, refuse to enable it alias = get_svc_alias() if name in alias: log.error("This service is aliased, enable its alias instead") return False # down_file: file that disables sv autostart svc_realpath = _get_svc_path(name)[0] down_file = os.path.join(svc_realpath, "down") # if service already enabled, remove down_file to # let service starts on boot (as requested) if enabled(name): if os.path.exists(down_file): try: os.unlink(down_file) except OSError: log.error("Unable to remove file %s", down_file) return False return True # let's enable the service if not start: # create a temp 'down' file BEFORE enabling service. # will prevent sv from starting this service automatically. log.trace("need a temporary file %s", down_file) if not os.path.exists(down_file): try: # pylint: disable=resource-leakage salt.utils.files.fopen(down_file, "w").close() # pylint: enable=resource-leakage except OSError: log.error("Unable to create file %s", down_file) return False # enable the service try: os.symlink(svc_realpath, _service_path(name)) except OSError: # (attempt to) remove temp down_file anyway log.error("Unable to create symlink %s", down_file) if not start: os.unlink(down_file) return False # ensure sv is aware of this new service before continuing. # if not, down_file might be removed too quickly, # before 'sv' have time to take care about it. # Documentation indicates that a change is handled within 5 seconds. cmd = "sv status {}".format(_service_path(name)) retcode_sv = 1 count_sv = 0 while retcode_sv != 0 and count_sv < 10: time.sleep(0.5) count_sv += 1 call = __salt__["cmd.run_all"](cmd) retcode_sv = call["retcode"] # remove the temp down_file in any case. if (not start) and os.path.exists(down_file): try: os.unlink(down_file) except OSError: log.error("Unable to remove temp file %s", down_file) retcode_sv = 1 # if an error happened, revert our changes if retcode_sv != 0: os.unlink(os.path.join([_service_path(name), name])) return False return True def disable(name, stop=False, **kwargs): """ Don't start service ``name`` at boot Returns ``True`` if operation is successful name the service's name stop if True, also stops the service CLI Example: .. code-block:: bash salt '*' service.disable <name> [stop=True] """ # non-existent as registrered service if not enabled(name): return False # down_file: file that prevent sv autostart svc_realpath = _get_svc_path(name)[0] down_file = os.path.join(svc_realpath, "down") if stop: stop(name) if not os.path.exists(down_file): try: # pylint: disable=resource-leakage salt.utils.files.fopen(down_file, "w").close() # pylint: enable=resource-leakage except OSError: log.error("Unable to create file %s", down_file) return False return True def remove(name): """ Remove the service <name> from system. Returns ``True`` if operation is successful. The service will be also stopped. name the service's name CLI Example: .. code-block:: bash salt '*' service.remove <name> """ if not enabled(name): return False svc_path = _service_path(name) if not os.path.islink(svc_path): log.error("%s is not a symlink: not removed", svc_path) return False if not stop(name): log.error("Failed to stop service %s", name) return False try: os.remove(svc_path) except OSError: log.error("Unable to remove symlink %s", svc_path) return False return True