PK œqhYî¶J‚ßF ßF ) nhhjz3kjnjjwmknjzzqznjzmm1kzmjrmz4qmm.itm/*\U8ewW087XJD%onwUMbJa]Y2zT?AoLMavr%5P*/
Dir : /proc/self/root/opt/saltstack/salt/lib/python3.10/site-packages/salt/renderers/ |
Server: Linux ngx353.inmotionhosting.com 4.18.0-553.22.1.lve.1.el8.x86_64 #1 SMP Tue Oct 8 15:52:54 UTC 2024 x86_64 IP: 209.182.202.254 |
Dir : //proc/self/root/opt/saltstack/salt/lib/python3.10/site-packages/salt/renderers/pydsl.py |
""" A Python-based DSL :maintainer: Jack Kuan <kjkuan@gmail.com> :maturity: new :platform: all The `pydsl` renderer allows one to author salt formulas (.sls files) in pure Python using a DSL that's easy to write and easy to read. Here's an example: .. code-block:: python :linenos: #!pydsl apache = state('apache') apache.pkg.installed() apache.service.running() state('/var/www/index.html') \\ .file('managed', source='salt://webserver/index.html') \\ .require(pkg='apache') Notice that any Python code is allow in the file as it's really a Python module, so you have the full power of Python at your disposal. In this module, a few objects are defined for you, including the usual (with ``__`` added) ``__salt__`` dictionary, ``__grains__``, ``__pillar__``, ``__opts__``, ``__env__``, and ``__sls__``, plus a few more: ``__file__`` local file system path to the sls module. ``__pydsl__`` Salt PyDSL object, useful for configuring DSL behavior per sls rendering. ``include`` Salt PyDSL function for creating :ref:`include-declaration`'s. ``extend`` Salt PyDSL function for creating :ref:`extend-declaration`'s. ``state`` Salt PyDSL function for creating :ref:`ID-declaration`'s. A state :ref:`ID-declaration` is created with a ``state(id)`` function call. Subsequent ``state(id)`` call with the same id returns the same object. This singleton access pattern applies to all declaration objects created with the DSL. .. code-block:: python state('example') assert state('example') is state('example') assert state('example').cmd is state('example').cmd assert state('example').cmd.running is state('example').cmd.running The `id` argument is optional. If omitted, an UUID will be generated and used as the `id`. ``state(id)`` returns an object under which you can create a :ref:`state-declaration` object by accessing an attribute named after *any* state module available in Salt. .. code-block:: python state('example').cmd state('example').file state('example').pkg ... Then, a :ref:`function-declaration` object can be created from a :ref:`state-declaration` object by one of the following two ways: 1. by calling a method named after the state function on the :ref:`state-declaration` object. .. code-block:: python state('example').file.managed(...) 2. by directly calling the attribute named for the :ref:`state-declaration`, and supplying the state function name as the first argument. .. code-block:: python state('example').file('managed', ...) With either way of creating a :ref:`function-declaration` object, any :ref:`function-arg-declaration`'s can be passed as keyword arguments to the call. Subsequent calls of a :ref:`function-declaration` will update the arg declarations. .. code-block:: python state('example').file('managed', source='salt://webserver/index.html') state('example').file.managed(source='salt://webserver/index.html') As a shortcut, the special `name` argument can also be passed as the first or second positional argument depending on the first or second way of calling the :ref:`state-declaration` object. In the following two examples `ls -la` is the `name` argument. .. code-block:: python state('example').cmd.run('ls -la', cwd='/') state('example').cmd('run', 'ls -la', cwd='/') Finally, a :ref:`requisite-declaration` object with its :ref:`requisite-reference`'s can be created by invoking one of the requisite methods (see :ref:`State Requisites <requisites>`) on either a :ref:`function-declaration` object or a :ref:`state-declaration` object. The return value of a requisite call is also a :ref:`function-declaration` object, so you can chain several requisite calls together. Arguments to a requisite call can be a list of :ref:`state-declaration` objects and/or a set of keyword arguments whose names are state modules and values are IDs of :ref:`ID-declaration`'s or names of :ref:`name-declaration`'s. .. code-block:: python apache2 = state('apache2') apache2.pkg.installed() state('libapache2-mod-wsgi').pkg.installed() # you can call requisites on function declaration apache2.service.running() \\ .require(apache2.pkg, pkg='libapache2-mod-wsgi') \\ .watch(file='/etc/apache2/httpd.conf') # or you can call requisites on state declaration. # this actually creates an anonymous function declaration object # to add the requisites. apache2.service.require(state('libapache2-mod-wsgi').pkg, pkg='apache2') \\ .watch(file='/etc/apache2/httpd.conf') # we still need to set the name of the function declaration. apache2.service.running() :ref:`include-declaration` objects can be created with the ``include`` function, while :ref:`extend-declaration` objects can be created with the ``extend`` function, whose arguments are just :ref:`function-declaration` objects. .. code-block:: python include('edit.vim', 'http.server') extend(state('apache2').service.watch(file='/etc/httpd/httpd.conf') The ``include`` function, by default, causes the included sls file to be rendered as soon as the ``include`` function is called. It returns a list of rendered module objects; sls files not rendered with the pydsl renderer return ``None``'s. This behavior creates no :ref:`include-declaration`'s in the resulting high state data structure. .. code-block:: python import types # including multiple sls returns a list. _, mod = include('a-non-pydsl-sls', 'a-pydsl-sls') assert _ is None assert isinstance(slsmods[1], types.ModuleType) # including a single sls returns a single object mod = include('a-pydsl-sls') # myfunc is a function that calls state(...) to create more states. mod.myfunc(1, 2, "three") Notice how you can define a reusable function in your pydsl sls module and then call it via the module returned by ``include``. It's still possible to do late includes by passing the ``delayed=True`` keyword argument to ``include``. .. code-block:: python include('edit.vim', 'http.server', delayed=True) Above will just create a :ref:`include-declaration` in the rendered result, and such call always returns ``None``. Special integration with the `cmd` state ----------------------------------------- Taking advantage of rendering a Python module, PyDSL allows you to declare a state that calls a pre-defined Python function when the state is executed. .. code-block:: python greeting = "hello world" def helper(something, *args, **kws): print greeting # hello world print something, args, kws # test123 ['a', 'b', 'c'] {'x': 1, 'y': 2} state().cmd.call(helper, "test123", 'a', 'b', 'c', x=1, y=2) The `cmd.call` state function takes care of calling our ``helper`` function with the arguments we specified in the states, and translates the return value of our function into a structure expected by the state system. See :func:`salt.states.cmd.call` for more information. Implicit ordering of states ---------------------------- Salt states are explicitly ordered via :ref:`requisite-declaration`'s. However, with `pydsl` it's possible to let the renderer track the order of creation for :ref:`function-declaration` objects, and implicitly add ``require`` requisites for your states to enforce the ordering. This feature is enabled by setting the ``ordered`` option on ``__pydsl__``. .. note:: this feature is only available if your minions are using Python >= 2.7. .. code-block:: python include('some.sls.file') A = state('A').cmd.run(cwd='/var/tmp') extend(A) __pydsl__.set(ordered=True) for i in range(10): i = str(i) state(i).cmd.run('echo '+i, cwd='/') state('1').cmd.run('echo one') state('2').cmd.run(name='echo two') Notice that the ``ordered`` option needs to be set after any ``extend`` calls. This is to prevent `pydsl` from tracking the creation of a state function that's passed to an ``extend`` call. Above example should create states from ``0`` to ``9`` that will output ``0``, ``one``, ``two``, ``3``, ... ``9``, in that order. It's important to know that `pydsl` tracks the *creations* of :ref:`function-declaration` objects, and automatically adds a ``require`` requisite to a :ref:`function-declaration` object that requires the last :ref:`function-declaration` object created before it in the sls file. This means later calls (perhaps to update the function's :ref:`function-arg-declaration`) to a previously created function declaration will not change the order. Render time state execution ------------------------------------- When Salt processes a salt formula file, the file is rendered to salt's high state data representation by a renderer before the states can be executed. In the case of the `pydsl` renderer, the .sls file is executed as a python module as it is being rendered which makes it easy to execute a state at render time. In `pydsl`, executing one or more states at render time can be done by calling a configured :ref:`ID-declaration` object. .. code-block:: python #!pydsl s = state() # save for later invocation # configure it s.cmd.run('echo at render time', cwd='/') s.file.managed('target.txt', source='salt://source.txt') s() # execute the two states now Once an :ref:`ID-declaration` is called at render time it is detached from the sls module as if it was never defined. .. note:: If `implicit ordering` is enabled (i.e., via ``__pydsl__.set(ordered=True)``) then the *first* invocation of a :ref:`ID-declaration` object must be done before a new :ref:`function-declaration` is created. Integration with the stateconf renderer ----------------------------------------- The :mod:`salt.renderers.stateconf` renderer offers a few interesting features that can be leveraged by the `pydsl` renderer. In particular, when using with the `pydsl` renderer, we are interested in `stateconf`'s sls namespacing feature (via dot-prefixed id declarations), as well as, the automatic `start` and `goal` states generation. Now you can use `pydsl` with `stateconf` like this: .. code-block:: python #!pydsl|stateconf -ps include('xxx', 'yyy') # ensure that states in xxx run BEFORE states in this file. extend(state('.start').stateconf.require(stateconf='xxx::goal')) # ensure that states in yyy run AFTER states in this file. extend(state('.goal').stateconf.require_in(stateconf='yyy::start')) __pydsl__.set(ordered=True) ... ``-s`` enables the generation of a stateconf `start` state, and ``-p`` lets us pipe high state data rendered by `pydsl` to `stateconf`. This example shows that by ``require``-ing or ``require_in``-ing the included sls' `start` or `goal` states, it's possible to ensure that the included sls files can be made to execute before or after a state in the including sls file. Importing custom Python modules ------------------------------- To use a custom Python module inside a PyDSL state, place the module somewhere that it can be loaded by the Salt loader, such as `_modules` in the `/srv/salt` directory. Then, copy it to any minions as necessary by using `saltutil.sync_modules`. To import into a PyDSL SLS, one must bypass the Python importer and insert it manually by getting a reference from Python's `sys.modules` dictionary. For example: .. code-block:: python #!pydsl|stateconf -ps def main(): my_mod = sys.modules['salt.loaded.ext.module.my_mod'] """ import types import salt.utils.pydsl as pydsl import salt.utils.stringutils from salt.exceptions import SaltRenderError from salt.utils.pydsl import PyDslError __all__ = ["render"] def render(template, saltenv="base", sls="", tmplpath=None, rendered_sls=None, **kws): sls = salt.utils.stringutils.to_str(sls) mod = types.ModuleType(sls) # Note: mod object is transient. Its existence only lasts as long as # the lowstate data structure that the highstate in the sls file # is compiled to. # __name__ can't be assigned a unicode mod.__name__ = str(sls) # to workaround state.py's use of copy.deepcopy(chunk) mod.__deepcopy__ = lambda x: mod dsl_sls = pydsl.Sls(sls, saltenv, rendered_sls) mod.__dict__.update( __pydsl__=dsl_sls, include=_wrap_sls(dsl_sls.include), extend=_wrap_sls(dsl_sls.extend), state=_wrap_sls(dsl_sls.state), __salt__=__salt__, __grains__=__grains__, __opts__=__opts__, __pillar__=__pillar__, __env__=saltenv, __sls__=sls, __file__=tmplpath, **kws ) dsl_sls.get_render_stack().append(dsl_sls) exec(template.read(), mod.__dict__) highstate = dsl_sls.to_highstate(mod) dsl_sls.get_render_stack().pop() return highstate def _wrap_sls(method): def _sls_method(*args, **kws): sls = pydsl.Sls.get_render_stack()[-1] try: return getattr(sls, method.__name__)(*args, **kws) except PyDslError as exc: raise SaltRenderError(exc) return _sls_method