1"""
2This runner makes Salt's
3execution modules available
4on the salt master.
5
6.. versionadded:: 2016.11.0
7
8.. _salt_salt_runner:
9
10Salt's execution modules are normally available
11on the salt minion. Use this runner to call
12execution modules on the salt master.
13Salt :ref:`execution modules <writing-execution-modules>`
14are the functions called by the ``salt`` command.
15
16Execution modules can be called with ``salt-run``:
17
18.. code-block:: bash
19
20    salt-run salt.cmd test.ping
21    # call functions with arguments and keyword arguments
22    salt-run salt.cmd test.arg 1 2 3 key=value a=1
23
24Execution modules are also available to salt runners:
25
26.. code-block:: python
27
28    __salt__['salt.cmd'](fun=fun, args=args, kwargs=kwargs)
29
30"""
31
32import copy
33import logging
34
35import salt.client
36import salt.loader
37import salt.pillar
38import salt.utils.args
39from salt.exceptions import SaltClientError
40
41log = logging.getLogger(__name__)
42
43
44def cmd(fun, *args, **kwargs):
45    """
46    .. versionchanged:: 2018.3.0
47        Added ``with_pillar`` argument
48
49    Execute ``fun`` with the given ``args`` and ``kwargs``.  Parameter ``fun``
50    should be the string :ref:`name <all-salt.modules>` of the execution module
51    to call.
52
53    .. note::
54        Execution modules will be loaded *every time* this function is called.
55        Additionally, keep in mind that since runners execute on the master,
56        custom execution modules will need to be synced to the master using
57        :py:func:`salt-run saltutil.sync_modules
58        <salt.runners.saltutil.sync_modules>`, otherwise they will not be
59        available.
60
61    with_pillar : False
62        If ``True``, pillar data will be compiled for the master
63
64        .. note::
65            To target the master in the pillar top file, keep in mind that the
66            default ``id`` for the master is ``<hostname>_master``. This can be
67            overridden by setting an ``id`` configuration parameter in the
68            master config file.
69
70    CLI Example:
71
72    .. code-block:: bash
73
74        salt-run salt.cmd test.ping
75        # call functions with arguments and keyword arguments
76        salt-run salt.cmd test.arg 1 2 3 a=1
77        salt-run salt.cmd mymod.myfunc with_pillar=True
78    """
79    log.debug("Called salt.cmd runner with minion function %s", fun)
80
81    kwargs = salt.utils.args.clean_kwargs(**kwargs)
82    with_pillar = kwargs.pop("with_pillar", False)
83
84    opts = copy.deepcopy(__opts__)
85    opts["grains"] = salt.loader.grains(opts)
86
87    if with_pillar:
88        opts["pillar"] = salt.pillar.get_pillar(
89            opts,
90            opts["grains"],
91            opts["id"],
92            saltenv=opts["saltenv"],
93            pillarenv=opts.get("pillarenv"),
94        ).compile_pillar()
95    else:
96        opts["pillar"] = {}
97
98    functions = salt.loader.minion_mods(
99        opts, utils=salt.loader.utils(opts), context=__context__
100    )
101
102    return (
103        functions[fun](*args, **kwargs)
104        if fun in functions
105        else "'{}' is not available.".format(fun)
106    )
107
108
109def execute(
110    tgt,
111    fun,
112    arg=(),
113    timeout=None,
114    tgt_type="glob",
115    ret="",
116    jid="",
117    kwarg=None,
118    **kwargs
119):
120    """
121    .. versionadded:: 2017.7.0
122
123    Execute ``fun`` on all minions matched by ``tgt`` and ``tgt_type``.
124    Parameter ``fun`` is the name of execution module function to call.
125
126    This function should mainly be used as a helper for runner modules,
127    in order to avoid redundant code.
128    For example, when inside a runner one needs to execute a certain function
129    on arbitrary groups of minions, only has to:
130
131    .. code-block:: python
132
133        ret1 = __salt__['salt.execute']('*', 'mod.fun')
134        ret2 = __salt__['salt.execute']('my_nodegroup', 'mod2.fun2', tgt_type='nodegroup')
135
136    It can also be used to schedule jobs directly on the master, for example:
137
138    .. code-block:: yaml
139
140        schedule:
141            collect_bgp_stats:
142                function: salt.execute
143                args:
144                    - edge-routers
145                    - bgp.neighbors
146                kwargs:
147                    tgt_type: nodegroup
148                days: 1
149                returner: redis
150    """
151    with salt.client.get_local_client(__opts__["conf_file"]) as client:
152        try:
153            return client.cmd(
154                tgt,
155                fun,
156                arg=arg,
157                timeout=timeout or __opts__["timeout"],
158                tgt_type=tgt_type,  # no warn_until, as this is introduced only in 2017.7.0
159                ret=ret,
160                jid=jid,
161                kwarg=kwarg,
162                **kwargs
163            )
164        except SaltClientError as client_error:
165            log.error("Error while executing %s on %s (%s)", fun, tgt, tgt_type)
166            log.error(client_error)
167            return {}
168