View file File name : __init__.py Content :""" Make api awesomeness """ import copy import inspect import logging import os import salt.auth import salt.client import salt.client.ssh.client import salt.config import salt.daemons.masterapi import salt.exceptions import salt.log # pylint: disable=W0611 import salt.runner import salt.syspaths import salt.utils.args import salt.utils.minions import salt.wheel from salt.defaults import DEFAULT_TARGET_DELIM log = logging.getLogger(__name__) class NetapiClient: """ Provide a uniform method of accessing the various client interfaces in Salt in the form of low-data data structures. For example: >>> client = NetapiClient(__opts__) >>> lowstate = {'client': 'local', 'tgt': '*', 'fun': 'test.ping', 'arg': ''} >>> client.run(lowstate) """ def __init__(self, opts): self.opts = opts apiopts = copy.deepcopy(self.opts) apiopts["enable_ssh_minions"] = True apiopts["cachedir"] = os.path.join(opts["cachedir"], "saltapi") if not os.path.exists(apiopts["cachedir"]): os.makedirs(apiopts["cachedir"]) self.resolver = salt.auth.Resolver(apiopts) self.loadauth = salt.auth.LoadAuth(apiopts) self.key = salt.daemons.masterapi.access_keys(apiopts) self.ckminions = salt.utils.minions.CkMinions(apiopts) def _is_master_running(self): """ Perform a lightweight check to see if the master daemon is running Note, this will return an invalid success if the master crashed or was not shut down cleanly. """ # Windows doesn't have IPC. Assume the master is running. # At worse, it will error 500. if salt.utils.platform.is_windows(): return True if self.opts["transport"] == "tcp": ipc_file = "publish_pull.ipc" else: ipc_file = "workers.ipc" return os.path.exists(os.path.join(self.opts["sock_dir"], ipc_file)) def _prep_auth_info(self, clear_load): sensitive_load_keys = [] key = None if "token" in clear_load: auth_type = "token" err_name = "TokenAuthenticationError" sensitive_load_keys = ["token"] return auth_type, err_name, key, sensitive_load_keys elif "eauth" in clear_load: auth_type = "eauth" err_name = "EauthAuthenticationError" sensitive_load_keys = ["username", "password"] return auth_type, err_name, key, sensitive_load_keys raise salt.exceptions.EauthAuthenticationError( "No authentication credentials given" ) def _authorize_ssh(self, low): auth_type, err_name, key, sensitive_load_keys = self._prep_auth_info(low) auth_check = self.loadauth.check_authentication(low, auth_type, key=key) auth_list = auth_check.get("auth_list", []) error = auth_check.get("error") if error: raise salt.exceptions.EauthAuthenticationError(error) delimiter = low.get("kwargs", {}).get("delimiter", DEFAULT_TARGET_DELIM) _res = self.ckminions.check_minions( low["tgt"], low.get("tgt_type", "glob"), delimiter ) minions = _res.get("minions", list()) missing = _res.get("missing", list()) authorized = self.ckminions.auth_check( auth_list, low["fun"], low.get("arg", []), low["tgt"], low.get("tgt_type", "glob"), minions=minions, ) if not authorized: raise salt.exceptions.EauthAuthenticationError( "Authorization error occurred." ) def run(self, low): """ Execute the specified function in the specified client by passing the lowstate """ # Eauth currently requires a running daemon and commands run through # this method require eauth so perform a quick check to raise a # more meaningful error. if not self._is_master_running(): raise salt.exceptions.SaltDaemonNotRunning("Salt Master is not available.") if low.get("client") not in CLIENTS: raise salt.exceptions.SaltInvocationError( "Invalid client specified: '{}'".format(low.get("client")) ) if not ("token" in low or "eauth" in low): raise salt.exceptions.EauthAuthenticationError( "No authentication credentials given" ) if low.get("raw_shell") and not self.opts.get("netapi_allow_raw_shell"): raise salt.exceptions.EauthAuthenticationError( "Raw shell option not allowed." ) if low["client"] == "ssh": self._authorize_ssh(low) l_fun = getattr(self, low["client"]) f_call = salt.utils.args.format_call(l_fun, low) return l_fun(*f_call.get("args", ()), **f_call.get("kwargs", {})) def local_async(self, *args, **kwargs): """ Run :ref:`execution modules <all-salt.modules>` asynchronously Wraps :py:meth:`salt.client.LocalClient.run_job`. :return: job ID """ with salt.client.get_local_client(mopts=self.opts) as client: return client.run_job(*args, **kwargs) def local(self, *args, **kwargs): """ Run :ref:`execution modules <all-salt.modules>` synchronously See :py:meth:`salt.client.LocalClient.cmd` for all available parameters. Sends a command from the master to the targeted minions. This is the same interface that Salt's own CLI uses. Note the ``arg`` and ``kwarg`` parameters are sent down to the minion(s) and the given function, ``fun``, is called with those parameters. :return: Returns the result from the execution module """ with salt.client.get_local_client(mopts=self.opts) as client: return client.cmd(*args, **kwargs) def local_subset(self, *args, **kwargs): """ Run :ref:`execution modules <all-salt.modules>` against subsets of minions .. versionadded:: 2016.3.0 Wraps :py:meth:`salt.client.LocalClient.cmd_subset` """ with salt.client.get_local_client(mopts=self.opts) as client: return client.cmd_subset(*args, **kwargs) def local_batch(self, *args, **kwargs): """ Run :ref:`execution modules <all-salt.modules>` against batches of minions .. versionadded:: 0.8.4 Wraps :py:meth:`salt.client.LocalClient.cmd_batch` :return: Returns the result from the exeuction module for each batch of returns """ with salt.client.get_local_client(mopts=self.opts) as client: return client.cmd_batch(*args, **kwargs) def ssh(self, *args, **kwargs): """ Run salt-ssh commands synchronously Wraps :py:meth:`salt.client.ssh.client.SSHClient.cmd_sync`. :return: Returns the result from the salt-ssh command """ with salt.client.ssh.client.SSHClient( mopts=self.opts, disable_custom_roster=True ) as client: return client.cmd_sync(kwargs) def runner(self, fun, timeout=None, full_return=False, **kwargs): """ Run `runner modules <all-salt.runners>` synchronously Wraps :py:meth:`salt.runner.RunnerClient.cmd_sync`. Note that runner functions must be called using keyword arguments. Positional arguments are not supported. :return: Returns the result from the runner module """ kwargs["fun"] = fun runner = salt.runner.RunnerClient(self.opts) return runner.cmd_sync(kwargs, timeout=timeout, full_return=full_return) def runner_async(self, fun, **kwargs): """ Run `runner modules <all-salt.runners>` asynchronously Wraps :py:meth:`salt.runner.RunnerClient.cmd_async`. Note that runner functions must be called using keyword arguments. Positional arguments are not supported. :return: event data and a job ID for the executed function. """ kwargs["fun"] = fun runner = salt.runner.RunnerClient(self.opts) return runner.cmd_async(kwargs) def wheel(self, fun, **kwargs): """ Run :ref:`wheel modules <all-salt.wheel>` synchronously Wraps :py:meth:`salt.wheel.WheelClient.master_call`. Note that wheel functions must be called using keyword arguments. Positional arguments are not supported. :return: Returns the result from the wheel module """ kwargs["fun"] = fun wheel = salt.wheel.WheelClient(self.opts) return wheel.cmd_sync(kwargs) def wheel_async(self, fun, **kwargs): """ Run :ref:`wheel modules <all-salt.wheel>` asynchronously Wraps :py:meth:`salt.wheel.WheelClient.master_call`. Note that wheel functions must be called using keyword arguments. Positional arguments are not supported. :return: Returns the result from the wheel module """ kwargs["fun"] = fun wheel = salt.wheel.WheelClient(self.opts) return wheel.cmd_async(kwargs) CLIENTS = [ name for name, _ in inspect.getmembers(NetapiClient, predicate=None) if not (name == "run" or name.startswith("_")) ]