"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")