"Fossies" - the Fresh Open Source Software Archive

Member "salt-3002.2/salt/states/module.py" (18 Nov 2020, 18535 Bytes) of package /linux/misc/salt-3002.2.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Python source code syntax highlighting (style: standard) with prefixed line numbers. Alternatively you can here view or download the uninterpreted source code file. For more information about "module.py" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 3002.1_vs_3002.2.

    1 r"""
    2 Execution of Salt modules from within states
    3 ============================================
    4 
    5 .. note::
    6 
    7     There are two styles of calling ``module.run``. **The legacy style will no
    8     longer be available starting in the 3005 release.** To opt-in early to the
    9     new style you must add the following to your ``/etc/salt/minion`` config
   10     file:
   11 
   12     .. code-block:: yaml
   13 
   14         use_superseded:
   15           - module.run
   16 
   17 With `module.run` these states allow individual execution module calls to be
   18 made via states. Here's a contrived example, to show you how it's done:
   19 
   20 .. code-block:: yaml
   21 
   22     # New Style
   23     test.random_hash:
   24       module.run:
   25         - test.random_hash:
   26           - size: 42
   27           - hash_type: sha256
   28 
   29     # Legacy Style
   30     test.random_hash:
   31       module.run:
   32         - size: 42
   33         - hash_type: sha256
   34 
   35 In the new style, the state ID (``test.random_hash``, in this case) is
   36 irrelevant when using ``module.run``. It could have very well been written:
   37 
   38 .. code-block:: yaml
   39 
   40     Generate a random hash:
   41       module.run:
   42         - test.random_hash:
   43           - size: 42
   44           - hash_type: sha256
   45 
   46 For a simple state like that it's not a big deal, but if the module you're
   47 using has certain parameters, things can get cluttered, fast. Using the
   48 contrived custom module (stuck in ``/srv/salt/_modules/foo.py``, or your
   49 configured file_roots_):
   50 
   51 .. code-block:: python
   52 
   53     def bar(name, names, fun, state, saltenv):
   54         return "Name: {name} Names: {names} Fun: {fun} State: {state} Saltenv: {saltenv}".format(**locals())
   55 
   56 Your legacy state has to look like this:
   57 
   58 .. code-block:: yaml
   59 
   60     # Legacy style
   61     Unfortunate example:
   62       module.run:
   63       - name: foo.bar
   64       - m_name: Some name
   65       - m_names:
   66         - Such names
   67         - very wow
   68       - m_state: Arkansas
   69       - m_fun: Such fun
   70       - m_saltenv: Salty
   71 
   72 With the new style it's much cleaner:
   73 
   74 .. code-block:: yaml
   75 
   76     # New style
   77     Better:
   78       module.run:
   79       - foo.bar:
   80         - name: Some name
   81         - names:
   82           - Such names
   83           - very wow
   84         - state: Arkansas
   85         - fun: Such fun
   86         - saltenv: Salty
   87 
   88 The new style also allows multiple modules in one state. For instance, you can
   89 do this:
   90 
   91 .. code-block:: yaml
   92 
   93     Do many things:
   94       module.run:
   95         - test.random_hash:
   96           - size: 10
   97           - hash_type: md5
   98         # Note the `:` at the end
   99         - test.true:
  100         - test.arg:
  101           - this
  102           - has
  103           - args
  104           - and: kwargs
  105           - isn't: that neat?
  106         # Note the `:` at the end, too
  107         - test.version:
  108         - test.fib:
  109           - 4
  110 
  111 Where in the legacy style you would have had to split your states like this:
  112 
  113 .. code-block:: yaml
  114 
  115     test.random_hash:
  116       module.run:
  117         - size: 10
  118         - hash_type: md5
  119 
  120     test.nop:
  121       module.run
  122 
  123     test.arg:
  124       module.run:
  125         - args:
  126           - this
  127           - has
  128           - args
  129         - kwargs:
  130             and: kwargs
  131             isn't: that neat?
  132 
  133     test.version:
  134       module.run
  135 
  136 Another difference is that in the legacy style, unconsumed arguments to the
  137 ``module`` state were simply passed into the module function being executed:
  138 
  139 .. code-block:: yaml
  140 
  141     show off module.run with args:
  142       module.run:
  143         - name: test.random_hash
  144         - size: 42
  145         - hash_type: sha256
  146 
  147 The new style is much more explicit, with the arguments and keyword arguments
  148 being nested under the name of the function:
  149 
  150 .. code-block:: yaml
  151 
  152     show off module.run with args:
  153       module.run:
  154         # Note the lack of `name: `, and trailing `:`
  155         - test.random_hash:
  156           - size: 42
  157           - hash_type: sha256
  158 
  159 If the function takes ``*args``, they can be passed in as well:
  160 
  161 .. code-block:: yaml
  162 
  163     args and kwargs:
  164       module.run:
  165         - test.arg:
  166           - isn't
  167           - this
  168           - fun
  169           - this: that
  170           - salt: stack
  171 
  172 Modern Examples
  173 ---------------
  174 
  175 Here are some other examples using the modern ``module.run``:
  176 
  177 .. code-block:: yaml
  178 
  179     fetch_out_of_band:
  180       module.run:
  181         - git.fetch:
  182           - cwd: /path/to/my/repo
  183           - user: myuser
  184           - opts: '--all'
  185 
  186 A more complex example:
  187 
  188 .. code-block:: yaml
  189 
  190     eventsviewer:
  191       module.run:
  192         - task.create_task:
  193           - name: events-viewer
  194           - user_name: System
  195           - action_type: Execute
  196           - cmd: 'c:\netops\scripts\events_viewer.bat'
  197           - trigger_type: 'Daily'
  198           - start_date: '2017-1-20'
  199           - start_time: '11:59PM'
  200 
  201 It is sometimes desirable to trigger a function call after a state is executed,
  202 for this the :mod:`module.wait <salt.states.module.wait>` state can be used:
  203 
  204 .. code-block:: yaml
  205 
  206     add example to hosts:
  207       file.append:
  208         - name: /etc/hosts
  209         - text: 203.0.113.13     example.com
  210 
  211     # New Style
  212     mine.send:
  213       module.wait:
  214         # Again, note the trailing `:`
  215         - hosts.list_hosts:
  216         - watch:
  217           - file: add example to hosts
  218 
  219 Legacy (Default) Examples
  220 -------------------------
  221 
  222 If you're using the legacy ``module.run``, due to how the state system works,
  223 if a module function accepts an argument called, ``name``, then ``m_name`` must
  224 be used to specify that argument, to avoid a collision with the ``name``
  225 argument.
  226 
  227 Here is a list of keywords hidden by the state system, which must be prefixed
  228 with ``m_``:
  229 
  230 * fun
  231 * name
  232 * names
  233 * state
  234 * saltenv
  235 
  236 For example:
  237 
  238 .. code-block:: yaml
  239 
  240     disable_nfs:
  241       module.run:
  242         - name: service.disable
  243         - m_name: nfs
  244 
  245 Note that some modules read all or some of the arguments from a list of keyword
  246 arguments. For example:
  247 
  248 .. code-block:: yaml
  249 
  250     mine.send:
  251       module.run:
  252         - func: network.ip_addrs
  253         - kwargs:
  254             interface: eth0
  255 
  256 .. code-block:: yaml
  257 
  258     cloud.create:
  259       module.run:
  260         - func: cloud.create
  261         - provider: test-provider
  262         - m_names:
  263           - test-vlad
  264         - kwargs: {
  265               ssh_username: 'ubuntu',
  266               image: 'ami-8d6d9daa',
  267               securitygroup: 'default',
  268               size: 'c3.large',
  269               location: 'ap-northeast-1',
  270               delvol_on_destroy: 'True'
  271           }
  272 
  273 Other modules take the keyword arguments using this style:
  274 
  275 .. code-block:: yaml
  276 
  277      mac_enable_ssh:
  278        module.run:
  279          - name: system.set_remote_login
  280          - enable: True
  281 
  282 Another example that creates a recurring task that runs a batch file on a
  283 Windows system:
  284 
  285 .. code-block:: yaml
  286 
  287     eventsviewer:
  288       module.run:
  289         - name: task.create_task
  290         - m_name: 'events-viewer'
  291         - user_name: System
  292         - kwargs: {
  293               action_type: 'Execute',
  294               cmd: 'c:\netops\scripts\events_viewer.bat',
  295               trigger_type: 'Daily',
  296               start_date: '2017-1-20',
  297               start_time: '11:59PM'
  298         }
  299 
  300 .. _file_roots: https://docs.saltstack.com/en/latest/ref/configuration/master.html#file-roots
  301 """
  302 
  303 import salt.loader
  304 import salt.utils.args
  305 import salt.utils.functools
  306 import salt.utils.jid
  307 from salt.exceptions import SaltInvocationError
  308 from salt.ext.six.moves import range
  309 from salt.utils.decorators import with_deprecated
  310 
  311 
  312 def wait(name, **kwargs):
  313     """
  314     Run a single module function only if the watch statement calls it
  315 
  316     ``name``
  317         The module function to execute
  318 
  319     ``**kwargs``
  320         Pass any arguments needed to execute the function
  321 
  322     .. note::
  323         Like the :mod:`cmd.run <salt.states.cmd.run>` state, this state will
  324         return ``True`` but not actually execute, unless one of the following
  325         two things happens:
  326 
  327         1. The state has a :ref:`watch requisite <requisites-watch>`, and
  328            the state which it is watching changes.
  329 
  330         2. Another state has a :ref:`watch_in requisite
  331            <requisites-watch-in>` which references this state, and the state
  332            wth the ``watch_in`` changes.
  333     """
  334     return {"name": name, "changes": {}, "result": True, "comment": ""}
  335 
  336 
  337 # Alias module.watch to module.wait
  338 watch = salt.utils.functools.alias_function(wait, "watch")
  339 
  340 
  341 @with_deprecated(globals(), "Phosphorus", policy=with_deprecated.OPT_IN)
  342 def run(**kwargs):
  343     """
  344     Run a single module function or a range of module functions in a batch.
  345     Supersedes ``module.run`` function, which requires ``m_`` prefix to
  346     function-specific parameters.
  347 
  348     :param returner:
  349         Specify a common returner for the whole batch to send the return data
  350 
  351     :param kwargs:
  352         Pass any arguments needed to execute the function(s)
  353 
  354     .. code-block:: yaml
  355 
  356       some_id_of_state:
  357         module.run:
  358           - network.ip_addrs:
  359             - interface: eth0
  360           - cloud.create:
  361             - names:
  362               - test-isbm-1
  363               - test-isbm-2
  364             - ssh_username: sles
  365             - image: sles12sp2
  366             - securitygroup: default
  367             - size: 'c3.large'
  368             - location: ap-northeast-1
  369             - delvol_on_destroy: True
  370 
  371 
  372     :return:
  373     """
  374 
  375     if "name" in kwargs:
  376         kwargs.pop("name")
  377     ret = {
  378         "name": list(kwargs),
  379         "changes": {},
  380         "comment": "",
  381         "result": None,
  382     }
  383 
  384     functions = [func for func in kwargs if "." in func]
  385     missing = []
  386     tests = []
  387     for func in functions:
  388         func = func.split(":")[0]
  389         if func not in __salt__:
  390             missing.append(func)
  391         elif __opts__["test"]:
  392             tests.append(func)
  393 
  394     if tests or missing:
  395         ret["comment"] = " ".join(
  396             [
  397                 missing
  398                 and "Unavailable function{plr}: "
  399                 "{func}.".format(
  400                     plr=(len(missing) > 1 or ""), func=(", ".join(missing) or "")
  401                 )
  402                 or "",
  403                 tests
  404                 and "Function{plr} {func} to be "
  405                 "executed.".format(
  406                     plr=(len(tests) > 1 or ""), func=(", ".join(tests)) or ""
  407                 )
  408                 or "",
  409             ]
  410         ).strip()
  411         ret["result"] = not (missing or not tests)
  412 
  413     if ret["result"] is None:
  414         ret["result"] = True
  415 
  416         failures = []
  417         success = []
  418         for func in functions:
  419             _func = func.split(":")[0]
  420             try:
  421                 func_ret = _call_function(
  422                     _func, returner=kwargs.get("returner"), func_args=kwargs.get(func)
  423                 )
  424                 if not _get_result(func_ret, ret["changes"].get("ret", {})):
  425                     if isinstance(func_ret, dict):
  426                         failures.append(
  427                             "'{}' failed: {}".format(
  428                                 func, func_ret.get("comment", "(error message N/A)")
  429                             )
  430                         )
  431                     if func_ret is False:
  432                         failures.append("'{}': {}".format(func, func_ret))
  433                 else:
  434                     success.append(
  435                         "{}: {}".format(
  436                             func,
  437                             func_ret.get("comment", "Success")
  438                             if isinstance(func_ret, dict)
  439                             else func_ret,
  440                         )
  441                     )
  442                     ret["changes"][func] = func_ret
  443             except (SaltInvocationError, TypeError) as ex:
  444                 failures.append("'{}' failed: {}".format(func, ex))
  445         ret["comment"] = ", ".join(failures + success)
  446         ret["result"] = not bool(failures)
  447 
  448     return ret
  449 
  450 
  451 def _call_function(name, returner=None, func_args=None, func_kwargs=None):
  452     """
  453     Calls a function from the specified module.
  454 
  455     :param str name: module.function of the function to call
  456     :param dict returner: Returner specification to use.
  457     :param list func_args: List with args and dicts of kwargs (one dict per kwarg)
  458         to pass to the function.
  459     :return: Result of the function call
  460     """
  461     if func_args is None:
  462         func_args = []
  463 
  464     if func_kwargs is None:
  465         func_kwargs = {}
  466 
  467     mret = salt.utils.functools.call_function(__salt__[name], *func_args, **func_kwargs)
  468     if returner is not None:
  469         returners = salt.loader.returners(__opts__, __salt__)
  470         if returner in returners:
  471             returners[returner](
  472                 {
  473                     "id": __opts__["id"],
  474                     "ret": mret,
  475                     "fun": name,
  476                     "jid": salt.utils.jid.gen_jid(__opts__),
  477                 }
  478             )
  479     return mret
  480 
  481 
  482 def _run(name, **kwargs):
  483     """
  484     .. deprecated:: 2017.7.0
  485        Function name stays the same, behaviour will change.
  486 
  487     Run a single module function
  488 
  489     ``name``
  490         The module function to execute
  491 
  492     ``returner``
  493         Specify the returner to send the return of the module execution to
  494 
  495     ``kwargs``
  496         Pass any arguments needed to execute the function
  497     """
  498     ret = {"name": name, "changes": {}, "comment": "", "result": None}
  499     if name not in __salt__:
  500         ret["comment"] = "Module function {} is not available".format(name)
  501         ret["result"] = False
  502         return ret
  503 
  504     if __opts__["test"]:
  505         ret["comment"] = "Module function {} is set to execute".format(name)
  506         return ret
  507 
  508     aspec = salt.utils.args.get_function_argspec(__salt__[name])
  509     args = []
  510     defaults = {}
  511 
  512     arglen = 0
  513     deflen = 0
  514     if isinstance(aspec.args, list):
  515         arglen = len(aspec.args)
  516     if isinstance(aspec.defaults, tuple):
  517         deflen = len(aspec.defaults)
  518     # Match up the defaults with the respective args
  519     for ind in range(arglen - 1, -1, -1):
  520         minus = arglen - ind
  521         if deflen - minus > -1:
  522             defaults[aspec.args[ind]] = aspec.defaults[-minus]
  523     # overwrite passed default kwargs
  524     for arg in defaults:
  525         if arg == "name":
  526             if "m_name" in kwargs:
  527                 defaults[arg] = kwargs.pop("m_name")
  528         elif arg == "fun":
  529             if "m_fun" in kwargs:
  530                 defaults[arg] = kwargs.pop("m_fun")
  531         elif arg == "state":
  532             if "m_state" in kwargs:
  533                 defaults[arg] = kwargs.pop("m_state")
  534         elif arg == "saltenv":
  535             if "m_saltenv" in kwargs:
  536                 defaults[arg] = kwargs.pop("m_saltenv")
  537         if arg in kwargs:
  538             defaults[arg] = kwargs.pop(arg)
  539     missing = set()
  540     for arg in aspec.args:
  541         if arg == "name":
  542             rarg = "m_name"
  543         elif arg == "fun":
  544             rarg = "m_fun"
  545         elif arg == "names":
  546             rarg = "m_names"
  547         elif arg == "state":
  548             rarg = "m_state"
  549         elif arg == "saltenv":
  550             rarg = "m_saltenv"
  551         else:
  552             rarg = arg
  553         if rarg not in kwargs and arg not in defaults:
  554             missing.add(rarg)
  555             continue
  556         if arg in defaults:
  557             args.append(defaults[arg])
  558         else:
  559             args.append(kwargs.pop(rarg))
  560     if missing:
  561         comment = "The following arguments are missing:"
  562         for arg in missing:
  563             comment += " {}".format(arg)
  564         ret["comment"] = comment
  565         ret["result"] = False
  566         return ret
  567 
  568     if aspec.varargs:
  569         if aspec.varargs == "name":
  570             rarg = "m_name"
  571         elif aspec.varargs == "fun":
  572             rarg = "m_fun"
  573         elif aspec.varargs == "names":
  574             rarg = "m_names"
  575         elif aspec.varargs == "state":
  576             rarg = "m_state"
  577         elif aspec.varargs == "saltenv":
  578             rarg = "m_saltenv"
  579         else:
  580             rarg = aspec.varargs
  581 
  582         if rarg in kwargs:
  583             varargs = kwargs.pop(rarg)
  584 
  585             if not isinstance(varargs, list):
  586                 msg = "'{0}' must be a list."
  587                 ret["comment"] = msg.format(aspec.varargs)
  588                 ret["result"] = False
  589                 return ret
  590 
  591             args.extend(varargs)
  592 
  593     nkwargs = {}
  594     if aspec.keywords and aspec.keywords in kwargs:
  595         nkwargs = kwargs.pop(aspec.keywords)
  596         if not isinstance(nkwargs, dict):
  597             msg = "'{0}' must be a dict."
  598             ret["comment"] = msg.format(aspec.keywords)
  599             ret["result"] = False
  600             return ret
  601 
  602     try:
  603         if aspec.keywords:
  604             mret = __salt__[name](*args, **nkwargs)
  605         else:
  606             mret = __salt__[name](*args)
  607     except Exception as e:  # pylint: disable=broad-except
  608         ret["comment"] = "Module function {} threw an exception. Exception: {}".format(
  609             name, e
  610         )
  611         ret["result"] = False
  612         return ret
  613     else:
  614         if mret is not None or mret != {}:
  615             ret["changes"]["ret"] = mret
  616 
  617     if "returner" in kwargs:
  618         ret_ret = {
  619             "id": __opts__["id"],
  620             "ret": mret,
  621             "fun": name,
  622             "jid": salt.utils.jid.gen_jid(__opts__),
  623         }
  624         returners = salt.loader.returners(__opts__, __salt__)
  625         if kwargs["returner"] in returners:
  626             returners[kwargs["returner"]](ret_ret)
  627     ret["comment"] = "Module function {} executed".format(name)
  628     ret["result"] = _get_result(mret, ret["changes"])
  629 
  630     return ret
  631 
  632 
  633 def _get_result(func_ret, changes):
  634     res = True
  635     # if mret is a dict and there is retcode and its non-zero
  636     if isinstance(func_ret, dict) and func_ret.get("retcode", 0) != 0:
  637         res = False
  638         # if its a boolean, return that as the result
  639     elif isinstance(func_ret, bool):
  640         res = func_ret
  641     else:
  642         changes_ret = changes.get("ret", {})
  643         if isinstance(changes_ret, dict):
  644             if isinstance(changes_ret.get("result", {}), bool):
  645                 res = changes_ret.get("result", {})
  646             elif changes_ret.get("retcode", 0) != 0:
  647                 res = False
  648             # Explore dict in depth to determine if there is a
  649             # 'result' key set to False which sets the global
  650             # state result.
  651             else:
  652                 res = _get_dict_result(changes_ret)
  653 
  654     return res
  655 
  656 
  657 def _get_dict_result(node):
  658     ret = True
  659     for key, val in node.items():
  660         if key == "result" and val is False:
  661             ret = False
  662             break
  663         elif isinstance(val, dict):
  664             ret = _get_dict_result(val)
  665             if ret is False:
  666                 break
  667     return ret
  668 
  669 
  670 mod_watch = salt.utils.functools.alias_function(run, "mod_watch")