Mini Shell
"""
Make me some salt!
"""
import logging
import os
import warnings
import salt.utils.kinds as kinds
from salt.exceptions import SaltClientError, SaltSystemExit, get_error_message
from salt.utils import migrations
from salt.utils.platform import is_junos
from salt.utils.process import HAS_PSUTIL
# All salt related deprecation warnings should be shown once each!
warnings.filterwarnings(
"once", # Show once
"", # No deprecation message match
DeprecationWarning, # This filter is for DeprecationWarnings
r"^(salt|salt\.(.*))$", # Match module(s) 'salt' and 'salt.<whatever>'
append=True,
)
# While we are supporting Python2.6, hide nested with-statements warnings
warnings.filterwarnings(
"ignore",
"With-statements now directly support multiple context managers",
DeprecationWarning,
append=True,
)
# Filter the backports package UserWarning about being re-imported
warnings.filterwarnings(
"ignore",
"^Module backports was already imported from (.*), but (.*) is being added to"
" sys.path$",
UserWarning,
append=True,
)
# the try block below bypasses an issue at build time so that modules don't
# cause the build to fail
try:
import salt.utils.parsers
from salt.utils.network import ip_bracket
from salt.utils.verify import check_user, verify_env, verify_socket
except ImportError as exc:
if exc.args[0] != "No module named _msgpack":
raise
log = logging.getLogger(__name__)
class DaemonsMixin: # pylint: disable=no-init
"""
Uses the same functions for all daemons
"""
def verify_hash_type(self):
"""
Verify and display a nag-messsage to the log if vulnerable hash-type is used.
:return:
"""
if self.config["hash_type"].lower() in ["md5", "sha1"]:
log.warning(
"IMPORTANT: Do not use %s hashing algorithm! Please set "
'"hash_type" to sha256 in Salt %s config!',
self.config["hash_type"],
self.__class__.__name__,
)
def action_log_info(self, action):
"""
Say daemon starting.
:param action
:return:
"""
log.info("%s the Salt %s", action, self.__class__.__name__)
def start_log_info(self):
"""
Say daemon starting.
:return:
"""
log.info("The Salt %s is starting up", self.__class__.__name__)
def shutdown_log_info(self):
"""
Say daemon shutting down.
:return:
"""
log.info("The Salt %s is shut down", self.__class__.__name__)
def environment_failure(self, error):
"""
Log environment failure for the daemon and exit with the error code.
:param error:
:return:
"""
log.exception(
"Failed to create environment for %s: %s",
self.__class__.__name__,
get_error_message(error),
)
self.shutdown(error)
class Master(
salt.utils.parsers.MasterOptionParser, DaemonsMixin
): # pylint: disable=no-init
"""
Creates a master server
"""
def _handle_signals(self, signum, sigframe):
if hasattr(self.master, "process_manager"):
# escalate signal to the process manager processes
self.master.process_manager._handle_signals(signum, sigframe)
super()._handle_signals(signum, sigframe)
def verify_environment(self):
if not self.config["verify_env"]:
return
v_dirs = [
self.config["pki_dir"],
os.path.join(self.config["pki_dir"], "minions"),
os.path.join(self.config["pki_dir"], "minions_pre"),
os.path.join(self.config["pki_dir"], "minions_denied"),
os.path.join(self.config["pki_dir"], "minions_autosign"),
os.path.join(self.config["pki_dir"], "minions_rejected"),
self.config["cachedir"],
os.path.join(self.config["cachedir"], "jobs"),
os.path.join(self.config["cachedir"], "proc"),
self.config["sock_dir"],
self.config["token_dir"],
self.config["syndic_dir"],
self.config["sqlite_queue_dir"],
]
pki_dir = self.config["pki_dir"]
if (
self.config["cluster_id"]
and self.config["cluster_pki_dir"]
and self.config["cluster_pki_dir"] != self.config["pki_dir"]
):
v_dirs.extend(
[
self.config["cluster_pki_dir"],
os.path.join(self.config["cluster_pki_dir"], "peers"),
os.path.join(self.config["cluster_pki_dir"], "minions"),
os.path.join(self.config["cluster_pki_dir"], "minions_pre"),
os.path.join(self.config["cluster_pki_dir"], "minions_denied"),
os.path.join(self.config["cluster_pki_dir"], "minions_autosign"),
os.path.join(self.config["cluster_pki_dir"], "minions_rejected"),
]
)
pki_dir = [self.config["pki_dir"], self.config["cluster_pki_dir"]]
verify_env(
v_dirs,
self.config["user"],
permissive=self.config["permissive_pki_access"],
root_dir=self.config["root_dir"],
pki_dir=pki_dir,
)
def prepare(self):
"""
Run the preparation sequence required to start a salt master server.
If sub-classed, don't **ever** forget to run:
super(YourSubClass, self).prepare()
"""
super().prepare()
try:
self.verify_environment()
except OSError as error:
self.environment_failure(error)
self.action_log_info("Setting up")
# TODO: AIO core is separate from transport
if not verify_socket(
self.config["interface"],
self.config["publish_port"],
self.config["ret_port"],
):
self.shutdown(4, "The ports are not available to bind")
self.config["interface"] = ip_bracket(self.config["interface"])
migrations.migrate_paths(self.config)
# Late import so logging works correctly
import salt.master
self.master = salt.master.Master(self.config)
self.daemonize_if_required()
self.set_pidfile()
salt.utils.process.notify_systemd()
def start(self):
"""
Start the actual master.
If sub-classed, don't **ever** forget to run:
super(YourSubClass, self).start()
NOTE: Run any required code before calling `super()`.
"""
super().start()
if check_user(self.config["user"]):
self.action_log_info("Starting up")
self.verify_hash_type()
self.master.start()
def shutdown(self, exitcode=0, exitmsg=None):
"""
If sub-classed, run any shutdown operations on this method.
"""
self.shutdown_log_info()
msg = "The salt master is shutdown. "
if exitmsg is not None:
exitmsg = msg + exitmsg
else:
exitmsg = msg.strip()
super().shutdown(exitcode, exitmsg)
class Minion(
salt.utils.parsers.MinionOptionParser, DaemonsMixin
): # pylint: disable=no-init
"""
Create a minion server
"""
def _handle_signals(self, signum, sigframe): # pylint: disable=unused-argument
# escalate signal to the process manager processes
if hasattr(self.minion, "stop"):
self.minion.stop(signum)
super()._handle_signals(signum, sigframe)
# pylint: disable=no-member
def prepare(self):
"""
Run the preparation sequence required to start a salt minion.
If sub-classed, don't **ever** forget to run:
super(YourSubClass, self).prepare()
"""
super().prepare()
try:
if self.config["verify_env"]:
confd = self.config.get("default_include")
if confd:
# If 'default_include' is specified in config, then use it
if "*" in confd:
# Value is of the form "minion.d/*.conf"
confd = os.path.dirname(confd)
if not os.path.isabs(confd):
# If configured 'default_include' is not an absolute
# path, consider it relative to folder of 'conf_file'
# (/etc/salt by default)
confd = os.path.join(
os.path.dirname(self.config["conf_file"]), confd
)
else:
confd = os.path.join(
os.path.dirname(self.config["conf_file"]), "minion.d"
)
v_dirs = [
self.config["pki_dir"],
self.config["cachedir"],
self.config["sock_dir"],
self.config["extension_modules"],
confd,
]
verify_env(
v_dirs,
self.config["user"],
permissive=self.config["permissive_pki_access"],
root_dir=self.config["root_dir"],
pki_dir=self.config["pki_dir"],
)
except OSError as error:
self.environment_failure(error)
log.info('Setting up the Salt Minion "%s"', self.config["id"])
migrations.migrate_paths(self.config)
# Bail out if we find a process running and it matches out pidfile
if (HAS_PSUTIL and not self.claim_process_responsibility()) or (
not HAS_PSUTIL and self.check_running()
):
self.action_log_info("An instance is already running. Exiting")
self.shutdown(1)
transport = self.config.get("transport").lower()
try:
# Late import so logging works correctly
import salt.minion
# If the minion key has not been accepted, then Salt enters a loop
# waiting for it, if we daemonize later then the minion could halt
# the boot process waiting for a key to be accepted on the master.
# This is the latest safe place to daemonize
self.daemonize_if_required()
self.set_pidfile()
if self.config.get("master_type") == "func":
salt.minion.eval_master_func(self.config)
self.minion = salt.minion.MinionManager(self.config)
except Exception: # pylint: disable=broad-except
log.error(
"An error occured while setting up the minion manager", exc_info=True
)
self.shutdown(1)
def start(self):
"""
Start the actual minion.
If sub-classed, don't **ever** forget to run:
super(YourSubClass, self).start()
NOTE: Run any required code before calling `super()`.
"""
super().start()
while True:
try:
self._real_start()
except SaltClientError as exc:
# Restart for multi_master failover when daemonized
if self.options.daemon:
continue
break
def _real_start(self):
try:
if check_user(self.config["user"]):
self.action_log_info("Starting up")
self.verify_hash_type()
self.minion.tune_in()
if self.minion.restart:
raise SaltClientError("Minion could not connect to Master")
except (KeyboardInterrupt, SaltSystemExit) as error:
self.action_log_info("Stopping")
if isinstance(error, KeyboardInterrupt):
log.warning("Exiting on Ctrl-c")
self.shutdown()
else:
log.error(error)
self.shutdown(error.code)
def call(self, cleanup_protecteds):
"""
Start the actual minion as a caller minion.
cleanup_protecteds is list of yard host addresses that should not be
cleaned up this is to fix race condition when salt-caller minion starts up
If sub-classed, don't **ever** forget to run:
super(YourSubClass, self).start()
NOTE: Run any required code before calling `super()`.
"""
try:
self.prepare()
if check_user(self.config["user"]):
self.minion.opts["__role"] = kinds.APPL_KIND_NAMES[
kinds.applKinds.caller
]
self.minion.call_in()
except (KeyboardInterrupt, SaltSystemExit) as exc:
self.action_log_info("Stopping")
if isinstance(exc, KeyboardInterrupt):
log.warning("Exiting on Ctrl-c")
self.shutdown()
else:
log.error(exc)
self.shutdown(exc.code)
def shutdown(self, exitcode=0, exitmsg=None):
"""
If sub-classed, run any shutdown operations on this method.
:param exitcode
:param exitmsg
"""
self.action_log_info("Shutting down")
if hasattr(self, "minion") and hasattr(self.minion, "destroy"):
self.minion.destroy()
super().shutdown(
exitcode,
(
"The Salt {} is shutdown. {}".format(
self.__class__.__name__, (exitmsg or "")
).strip()
),
)
# pylint: enable=no-member
class ProxyMinion(
salt.utils.parsers.ProxyMinionOptionParser, DaemonsMixin
): # pylint: disable=no-init
"""
Create a proxy minion server
"""
def _handle_signals(self, signum, sigframe): # pylint: disable=unused-argument
# escalate signal to the process manager processes
self.minion.stop(signum)
super()._handle_signals(signum, sigframe)
# pylint: disable=no-member
def prepare(self):
"""
Run the preparation sequence required to start a salt proxy minion.
If sub-classed, don't **ever** forget to run:
super(YourSubClass, self).prepare()
"""
super().prepare()
## allow for native minion
if not is_junos():
if not self.values.proxyid:
self.error("salt-proxy requires --proxyid")
# Proxies get their ID from the command line. This may need to change in
# the future.
# We used to set this here. Now it is set in ProxyMinionOptionParser
# by passing it via setup_config to config.minion_config
# self.config['id'] = self.values.proxyid
try:
if self.config["verify_env"]:
confd = self.config.get("default_include")
if confd:
# If 'default_include' is specified in config, then use it
if "*" in confd:
# Value is of the form "minion.d/*.conf"
confd = os.path.dirname(confd)
if not os.path.isabs(confd):
# If configured 'default_include' is not an absolute
# path, consider it relative to folder of 'conf_file'
# (/etc/salt by default)
confd = os.path.join(
os.path.dirname(self.config["conf_file"]), confd
)
else:
confd = os.path.join(
os.path.dirname(self.config["conf_file"]), "proxy.d"
)
v_dirs = [
self.config["pki_dir"],
self.config["cachedir"],
self.config["sock_dir"],
self.config["extension_modules"],
confd,
]
verify_env(
v_dirs,
self.config["user"],
permissive=self.config["permissive_pki_access"],
root_dir=self.config["root_dir"],
pki_dir=self.config["pki_dir"],
)
except OSError as error:
self.environment_failure(error)
self.action_log_info('Setting up "{}"'.format(self.config["id"]))
migrations.migrate_paths(self.config)
# Bail out if we find a process running and it matches out pidfile
if self.check_running():
self.action_log_info("An instance is already running. Exiting")
self.shutdown(1)
# TODO: AIO core is separate from transport
# Late import so logging works correctly
import salt.minion
# If the minion key has not been accepted, then Salt enters a loop
# waiting for it, if we daemonize later then the minion could halt
# the boot process waiting for a key to be accepted on the master.
# This is the latest safe place to daemonize
self.daemonize_if_required()
self.set_pidfile()
if self.config.get("master_type") == "func":
salt.minion.eval_master_func(self.config)
self.minion = salt.minion.ProxyMinionManager(self.config)
def start(self):
"""
Start the actual proxy minion.
If sub-classed, don't **ever** forget to run:
super(YourSubClass, self).start()
NOTE: Run any required code before calling `super()`.
"""
super().start()
try:
if check_user(self.config["user"]):
self.action_log_info("The Proxy Minion is starting up")
self.verify_hash_type()
self.minion.tune_in()
if self.minion.restart:
raise SaltClientError("Proxy Minion could not connect to Master")
except (KeyboardInterrupt, SaltSystemExit) as exc:
self.action_log_info("Proxy Minion Stopping")
if isinstance(exc, KeyboardInterrupt):
log.warning("Exiting on Ctrl-c")
self.shutdown()
else:
log.error(exc)
self.shutdown(exc.code)
def shutdown(self, exitcode=0, exitmsg=None):
"""
If sub-classed, run any shutdown operations on this method.
:param exitcode
:param exitmsg
"""
if hasattr(self, "minion") and "proxymodule" in self.minion.opts:
proxy_fn = self.minion.opts["proxymodule"].loaded_base_name + ".shutdown"
self.minion.opts["proxymodule"][proxy_fn](self.minion.opts)
self.action_log_info("Shutting down")
super().shutdown(
exitcode,
(
"The Salt {} is shutdown. {}".format(
self.__class__.__name__, (exitmsg or "")
).strip()
),
)
# pylint: enable=no-member
class Syndic(
salt.utils.parsers.SyndicOptionParser, DaemonsMixin
): # pylint: disable=no-init
"""
Create a syndic server
"""
def prepare(self):
"""
Run the preparation sequence required to start a salt syndic minion.
If sub-classed, don't **ever** forget to run:
super(YourSubClass, self).prepare()
"""
super().prepare()
try:
if self.config["verify_env"]:
verify_env(
[
self.config["pki_dir"],
self.config["sock_dir"],
self.config["extension_modules"],
],
self.config["user"],
permissive=self.config["permissive_pki_access"],
root_dir=self.config["root_dir"],
pki_dir=self.config["pki_dir"],
)
except OSError as error:
self.environment_failure(error)
self.action_log_info('Setting up "{}"'.format(self.config["id"]))
# Late import so logging works correctly
import salt.minion
self.daemonize_if_required()
self.syndic = salt.minion.SyndicManager(self.config)
self.set_pidfile()
def start(self):
"""
Start the actual syndic.
If sub-classed, don't **ever** forget to run:
super(YourSubClass, self).start()
NOTE: Run any required code before calling `super()`.
"""
super().start()
if check_user(self.config["user"]):
self.action_log_info("Starting up")
self.verify_hash_type()
try:
self.syndic.tune_in()
except KeyboardInterrupt:
self.action_log_info("Stopping")
self.shutdown()
def shutdown(self, exitcode=0, exitmsg=None):
"""
If sub-classed, run any shutdown operations on this method.
:param exitcode
:param exitmsg
"""
self.action_log_info("Shutting down")
super().shutdown(
exitcode,
(
"The Salt {} is shutdown. {}".format(
self.__class__.__name__, (exitmsg or "")
).strip()
),
)
Zerion Mini Shell 1.0