diff --git a/changelog/68367.added.md b/changelog/68367.added.md new file mode 100644 index 000000000000..e6331b0432d1 --- /dev/null +++ b/changelog/68367.added.md @@ -0,0 +1 @@ +Added capability to specify module arguments in an sls as a dict diff --git a/doc/cheatsheet/salt.tex b/doc/cheatsheet/salt.tex index 9e85ebf14e26..e3e6709a0e0d 100644 --- a/doc/cheatsheet/salt.tex +++ b/doc/cheatsheet/salt.tex @@ -48,7 +48,7 @@ - running - enable: True /var/www/index.html: # ID declaration - file: # state declaration + file: # state module declaration - managed # function - source: salt://webserver/index.html - user: root diff --git a/doc/faq.rst b/doc/faq.rst index 5ae6e55287d0..60b96f6746d1 100644 --- a/doc/faq.rst +++ b/doc/faq.rst @@ -189,7 +189,7 @@ PATH using a :mod:`file.symlink ` state. /usr/bin/foo: file.symlink: - - target: /usr/local/bin/foo + target: /usr/local/bin/foo .. _which-version: @@ -240,10 +240,10 @@ via a :py:func:`file.managed ` state, using the /etc/my_super_secret_file: file.managed: - - user: secret - - group: secret - - mode: 600 - - contents_pillar: secret_files:my_super_secret_file + user: secret + group: secret + mode: 600 + contents_pillar: secret_files:my_super_secret_file In this example, the source file would be located in a directory called ``secret_files`` underneath the file_tree path for the minion. The syntax for @@ -284,38 +284,38 @@ service to restart right after the upgrade: Disable starting services: file.managed: - - name: /usr/sbin/policy-rc.d - - user: root - - group: root - - mode: 0755 - - contents: + name: /usr/sbin/policy-rc.d + user: root + group: root + mode: 0755 + contents: - '#!/bin/sh' - exit 101 # do not touch if already exists - - replace: False - - prereq: + replace: False + prereq: - pkg: Upgrade Salt Minion {%- endif %} Upgrade Salt Minion: pkg.installed: - - name: salt-minion - - version: 2016.11.3{% if grains['os_family'] == 'Debian' %}+ds-1{% endif %} - - order: last + name: salt-minion + version: 2016.11.3{% if grains['os_family'] == 'Debian' %}+ds-1{% endif %} + order: last Enable Salt Minion: service.enabled: - - name: salt-minion - - require: + name: salt-minion + require: - pkg: Upgrade Salt Minion {%- if grains['os_family'] == 'Debian' %} Enable starting services: file.absent: - - name: /usr/sbin/policy-rc.d - - onchanges: + name: /usr/sbin/policy-rc.d + onchanges: - pkg: Upgrade Salt Minion {%- endif %} @@ -331,9 +331,9 @@ The following example works on UNIX-like operating systems: {%- if grains['os'] != 'Windows' %} Restart Salt Minion: cmd.run: - - name: 'salt-call service.restart salt-minion' - - bg: True - - onchanges: + name: 'salt-call service.restart salt-minion' + bg: True + onchanges: - pkg: Upgrade Salt Minion {%- endif %} @@ -351,12 +351,12 @@ as follows: Restart Salt Minion: cmd.run: {%- if grains['kernel'] == 'Windows' %} - - name: 'C:\salt\salt-call.bat service.restart salt-minion' + name: 'C:\salt\salt-call.bat service.restart salt-minion' {%- else %} - - name: 'salt-call service.restart salt-minion' + name: 'salt-call service.restart salt-minion' {%- endif %} - - bg: True - - onchanges: + bg: True + onchanges: - pkg: Upgrade Salt Minion However, it requires more advanced tricks to upgrade from legacy version of @@ -374,10 +374,10 @@ should run last or watch for the ``pkg`` state changes: Restart Salt Minion: cmd.run: {%- if grains['kernel'] == 'Windows' %} - - name: 'start powershell "Restart-Service -Name salt-minion"' + name: 'start powershell "Restart-Service -Name salt-minion"' {%- else %} # fork and disown the process - - name: |- + name: |- exec 0>&- # close stdin exec 1>&- # close stdout exec 2>&- # close stderr @@ -408,15 +408,15 @@ needs to run on the master): Wait for salt minion: loop.until_no_eval: - - name: saltutil.runner - - expected: + name: saltutil.runner + expected: - my_minion - - args: + args: - manage.up - - kwargs: + kwargs: tgt: my_minion - - period: 3 - - init_wait: 3 + period: 3 + init_wait: 3 This will, after an initial delay of 3 seconds, execute the `manage.up`-runner targeted specifically for `my_minion`. It will do this every `period` seconds diff --git a/doc/glossary.rst b/doc/glossary.rst index 612acef1c0e7..ab714e20d3ff 100644 --- a/doc/glossary.rst +++ b/doc/glossary.rst @@ -223,6 +223,11 @@ Glossary set of host machines. Often used to create and deploy private clouds. *See also*: :py:mod:`virt runner `. + SLS + Structured Layered State. The SLS is a representation of the state in + which a system should be in, and is set up to contain this data in a + simple format. This is often called configuration management. + SLS Module Contains a set of :term:`state declarations `. diff --git a/doc/ref/configuration/minion.rst b/doc/ref/configuration/minion.rst index 1e2b8c51972a..250333aa9525 100644 --- a/doc/ref/configuration/minion.rst +++ b/doc/ref/configuration/minion.rst @@ -2969,12 +2969,12 @@ data in a state file: apply_showpillar: module.run: - - name: state.apply - - mods: + name: state.apply + mods: - showpillar - - kwargs: - pillar: - test: "foo bar" + kwargs: + pillar: + test: "foo bar" If set to ``True``, the ``showpillar`` state will have access to the global pillar data. diff --git a/doc/ref/file_server/environments.rst b/doc/ref/file_server/environments.rst index 7357ee0ffae6..b8eb2256575e 100644 --- a/doc/ref/file_server/environments.rst +++ b/doc/ref/file_server/environments.rst @@ -66,10 +66,10 @@ the file from the ``config`` environment: /etc/foo/bar.conf: file.managed: - - source: salt://foo/bar.conf - - user: foo - - mode: 600 - - saltenv: config + source: salt://foo/bar.conf + user: foo + mode: 600 + saltenv: config Another way of doing the same thing is to use the :ref:`querystring syntax ` described above: @@ -78,9 +78,9 @@ Another way of doing the same thing is to use the :ref:`querystring syntax /etc/foo/bar.conf: file.managed: - - source: salt://foo/bar.conf?saltenv=config - - user: foo - - mode: 600 + source: salt://foo/bar.conf?saltenv=config + user: foo + mode: 600 .. note:: Specifying the environment using either of the above methods is only diff --git a/doc/ref/file_server/index.rst b/doc/ref/file_server/index.rst index ad498d34ae7c..1c70115caff1 100644 --- a/doc/ref/file_server/index.rst +++ b/doc/ref/file_server/index.rst @@ -36,8 +36,8 @@ with ``salt://|`` rather than ``salt://``. /etc/marathon/conf/?checkpoint: file.managed: - - source: salt://|hw/config/?checkpoint - - makedirs: True + source: salt://|hw/config/?checkpoint + makedirs: True Environments ```````````` diff --git a/doc/ref/renderers/index.rst b/doc/ref/renderers/index.rst index da245f9214cb..3446b4c5da44 100644 --- a/doc/ref/renderers/index.rst +++ b/doc/ref/renderers/index.rst @@ -82,7 +82,7 @@ to install a package: """ return { "include": ["python"], - "python-foo": {"pkg.installed": [{"version": "1.5-1.el7"}]}, + "python-foo": {"pkg.installed": {"version": "1.5-1.el7"}}, } This would be equivalent to the following: @@ -94,7 +94,7 @@ This would be equivalent to the following: python-foo: pkg.installed: - - version: '1.5-1.el7' + version: '1.5-1.el7' .. _renderers-composing: @@ -132,10 +132,10 @@ render pipeline: An_Example: cmd.run: - - name: | + name: | echo "Using Salt ${grains['saltversion']}" \ "from path {{grains['saltpath']}}." - - cwd: / + cwd: / <%doc> ${...} is Mako's notation, and so is this comment. {# Similarly, {{...}} is Jinja's notation, and so is this comment. #} diff --git a/doc/ref/states/aggregate.rst b/doc/ref/states/aggregate.rst index 5e25dda8fce2..5693f69a0dbc 100644 --- a/doc/ref/states/aggregate.rst +++ b/doc/ref/states/aggregate.rst @@ -72,14 +72,14 @@ manager. lamp_stack: pkg.installed: - - pkgs: + pkgs: - php - mysql-client - - aggregate: True + aggregate: True memcached: pkg.installed: - - name: memcached + name: memcached Adding mod_aggregate to a State Module ====================================== diff --git a/doc/ref/states/backup_mode.rst b/doc/ref/states/backup_mode.rst index 3d33abde5504..63e5439f7868 100644 --- a/doc/ref/states/backup_mode.rst +++ b/doc/ref/states/backup_mode.rst @@ -21,8 +21,8 @@ Or it can be set for each file: /etc/ssh/sshd_config: file.managed: - - source: salt://ssh/sshd_config - - backup: minion + source: salt://ssh/sshd_config + backup: minion The backup_mode can be set to any of the following options: diff --git a/doc/ref/states/compiler_ordering.rst b/doc/ref/states/compiler_ordering.rst index 422beb39300d..b4240be5a948 100644 --- a/doc/ref/states/compiler_ordering.rst +++ b/doc/ref/states/compiler_ordering.rst @@ -60,10 +60,10 @@ As an example, a state written thusly: apache: pkg.installed: - - name: httpd + name: httpd service.running: - - name: httpd - - watch: + name: httpd + watch: - file: apache_conf - pkg: apache @@ -78,34 +78,24 @@ Will have High Data which looks like this represented in json: { "apache": { - "pkg": [ - { - "name": "httpd" - }, - "installed", - { - "order": 10000 - } - ], - "service": [ - { - "name": "httpd" - }, - { - "watch": [ - { - "file": "apache_conf" - }, - { - "pkg": "apache" - } - ] - }, - "running", - { - "order": 10001 - } - ], + "pkg": { + "name": "httpd", + "fun": "installed", + "order": 10000 + }, + "service": { + "name": "httpd", + "watch": [ + { + "file": "apache_conf" + }, + { + "pkg": "apache" + } + ], + "fun": "running", + "order": 10001 + }, "__sls__": "blah", "__env__": "base" }, @@ -247,8 +237,8 @@ ordering can be explicitly overridden using the ``order`` flag in states: apache: pkg.installed: - - name: httpd - - order: 1 + name: httpd + order: 1 This order flag will over ride the definition order, this makes it very simple to create states that are always executed first, last or in specific diff --git a/doc/ref/states/extend.rst b/doc/ref/states/extend.rst index 2526b1d98c9e..1470dfef6e92 100644 --- a/doc/ref/states/extend.rst +++ b/doc/ref/states/extend.rst @@ -1,3 +1,5 @@ +.. _extending-external-sls-data: + =========================== Extending External SLS Data =========================== @@ -9,10 +11,53 @@ overwritten or when a service needs to watch an additional state. The Extend Declaration ---------------------- -The standard way to extend is via the extend declaration. The extend -declaration is a top level declaration like ``include`` and encapsulates ID -declaration data included from other SLS files. A standard extend looks like -this: +A standard way to extend is via the extend declaration. The extend +declaration is a top level declaration like ``include`` that allows +overriding or augmenting state declarations from other SLS files. +Use ``extend`` to override arguments, append requisites, +or otherwise modify an existing ID without editing the original SLS. + +Overview +-------- + +- ``extend`` is a top-level mapping at the same syntactic level as ``include`` or a top-level ID. +- The SLS module that defines the target ID must be included so the ID exists before the + extend merge is applied. +- Requisite lists (for example ``watch`` and ``require``) are appended; most other keys + are replaced by the extend entry. +- Only one top-level ``extend`` mapping may appear in a single SLS file; later mappings + will overwrite earlier ones. + +Example +------- + +The following shows the original SLS entries (the files being extended) and an extending SLS +that includes them and declares a single ``extend`` block. + +Original: ``salt://http/init.sls`` + +.. code-block:: yaml + + apache: + pkg.installed: [] + file: + name: /etc/httpd/conf/httpd.conf + source: salt://http/httpd.conf + service.running: + name: httpd + watch: + - file: apache + +Original: ``salt://ssh/init.sls`` + +.. code-block:: yaml + + ssh-server: + pkg.installed: [] + service.running: + name: sshd + +Extending SLS: ``salt://profile/webserver_extend.sls`` .. code-block:: yaml @@ -23,22 +68,52 @@ this: extend: apache: file: - - name: /etc/httpd/conf/httpd.conf - - source: salt://http/httpd2.conf + name: /etc/httpd/conf/httpd.conf + source: salt://http/httpd2.conf + ssh-server: service: - - watch: + watch: - file: /etc/ssh/banner /etc/ssh/banner: file.managed: - - source: salt://ssh/banner + source: salt://ssh/banner + +Behavior for this example +------------------------- + +- The ``apache:file`` mapping in the extending SLS overrides with the + ``name`` and ``source`` values from the original ``file`` mapping + in ``http/init.sls`` with the values supplied under ``extend``. +- The ``ssh-server:service:watch`` list is appended with ``file: /etc/ssh/banner``; any + existing watch entries declared in ``ssh/init.sls`` are preserved. +- The banner resource is declared locally (``/etc/ssh/banner``) so the appended watch has + a concrete state to observe; if the resource were absent from the compiled data the + relationship would be invalid. -A few critical things happened here, first off the SLS files that are going to -be extended are included, then the extend dec is defined. Under the extend dec -2 IDs are extended, the apache ID's file state is overwritten with a new name -and source. Then the ssh server is extended to watch the banner file in -addition to anything it is already watching. +Minimal patterns +---------------- + +Replace a mapping (overwrite): + +.. code-block:: yaml + + extend: + apache: + file: + name: /etc/httpd/conf/httpd.conf + source: salt://http/httpd2.conf + +Append to a requisite list (merge): + +.. code-block:: yaml + + extend: + ssh-server: + service: + watch: + - file: /etc/ssh/banner Extend is a Top Level Declaration --------------------------------- @@ -55,13 +130,13 @@ twice then only one of the extend blocks will be read. So this is WRONG: extend: apache: file: - - name: /etc/httpd/conf/httpd.conf - - source: salt://http/httpd2.conf + name: /etc/httpd/conf/httpd.conf + source: salt://http/httpd2.conf # Second extend will overwrite the first!! Only make one extend: ssh-server: service: - - watch: + watch: - file: /etc/ssh/banner @@ -81,10 +156,13 @@ cleanly defined like so: /etc/ssh/banner: file.managed: - - source: salt://ssh/banner - - watch_in: + source: salt://ssh/banner + watch_in: - service: ssh-server +:ref:`State Requisites ` + + Rules to Extend By ------------------ There are a few rules to remember when extending states: diff --git a/doc/ref/states/failhard.rst b/doc/ref/states/failhard.rst index b19d59d90ab4..53f11aca5f41 100644 --- a/doc/ref/states/failhard.rst +++ b/doc/ref/states/failhard.rst @@ -25,12 +25,12 @@ A good example of this would be setting up a package manager early on: /etc/yum.repos.d/company.repo: file.managed: - - source: salt://company/yumrepo.conf - - user: root - - group: root - - mode: 644 - - order: 1 - - failhard: True + source: salt://company/yumrepo.conf + user: root + group: root + mode: 644 + order: 1 + failhard: True In this situation, the yum repo is going to be configured before other states, and if it fails to lay down the config file, than no other states will be diff --git a/doc/ref/states/highstate.rst b/doc/ref/states/highstate.rst index f5326df63422..170b57233b31 100644 --- a/doc/ref/states/highstate.rst +++ b/doc/ref/states/highstate.rst @@ -32,7 +32,7 @@ Configurable via :conf_master:`state_top`. Include declaration ------------------- -Defines a list of :ref:`module-reference` strings to include in this ``SLS``. +Defines a list of :ref:`sls-module-reference` strings to include in this ``SLS``. Occurs only in the top level of the SLS data structure. @@ -44,13 +44,14 @@ Example: - edit.vim - http.server -.. _module-reference: +.. _sls-module-reference: -Module reference ----------------- +SLS module reference +-------------------- -The name of a SLS module defined by a separate SLS file and residing on -the Salt Master. A module named ``edit.vim`` is a reference to the SLS +A reference to an SLS module defined by a separate SLS file or +directory residing on the Salt Master. +For example ``edit.vim`` is a reference to the SLS file ``salt://edit/vim.sls``. .. _id-declaration: @@ -58,14 +59,16 @@ file ``salt://edit/vim.sls``. ID declaration -------------- -Defines an individual :ref:`highstate ` component. Always -references a value of a dictionary containing keys referencing -:ref:`state-declaration` and :ref:`requisite-declaration`. Can be overridden by -a :ref:`name-declaration` or a :ref:`names-declaration`. +A label that identifies an individual :ref:`highstate ` component. +The ID is a reference to a dictionary containing entries of one or more +:ref:`state-declaration` components. +The ID is used as an implicit name argument for the state function for any of +the referenced state declarations that do not provide an +explicit name with a :ref:`name-declaration` or a :ref:`names-declaration`. Occurs on the top level or under the :ref:`extend-declaration`. -Must be unique across entire state tree. If the same ID declaration is +Must be unique across the entire state tree. If the same ID declaration is used twice, then a compilation error will occur. .. note:: Naming gotchas @@ -73,88 +76,35 @@ used twice, then a compilation error will occur. In Salt versions earlier than 0.9.7, ID declarations containing dots would result in unpredictable output. -.. _extend-declaration: - -Extend declaration ------------------- - -Extends a :ref:`name-declaration` from an included ``SLS module``. The -keys of the extend declaration always refer to an existing -:ref:`id-declaration` which have been defined in included ``SLS modules``. - -Occurs only in the top level and defines a dictionary. - -States cannot be extended more than once in a single state run. - -Extend declarations are useful for adding-to or overriding parts of a -:ref:`state-declaration` that is defined in another ``SLS`` file. In the -following contrived example, the shown ``mywebsite.sls`` file is ``include`` --ing and ``extend`` -ing the ``apache.sls`` module in order to add a ``watch`` -declaration that will restart Apache whenever the Apache configuration file, -``mywebsite`` changes. - -.. code-block:: yaml - - include: - - apache - - extend: - apache: - service: - - watch: - - file: mywebsite - - mywebsite: - file.managed: - - name: /var/www/mysite - -.. seealso:: watch_in and require_in - - Sometimes it is more convenient to use the :ref:`watch_in - ` or :ref:`require_in ` syntax - instead of extending another ``SLS`` file. - - :ref:`State Requisites ` - .. _state-declaration: State declaration ----------------- -A list which contains one string defining the :ref:`function-declaration` and -any number of :ref:`function-arg-declaration` dictionaries. +A state declaration consists of a :ref:`state-module-declaration`, +a :ref:`function-declaration` and any number of +:ref:`function-arg-declaration` items. Can, optionally, contain a number of additional components like the -name override components — :ref:`name ` and +name components — :ref:`name ` and :ref:`names `. Can also contain :ref:`requisite declarations `. Occurs under an :ref:`ID-declaration`. -.. _requisite-declaration: +.. _state-module-declaration: -Requisite declaration ---------------------- - -A list containing :ref:`requisite references `. - -Used to build the action dependency tree. While Salt states are made to -execute in a deterministic order, this order is managed by requiring -and watching other Salt states. - -Occurs as a list component under a :ref:`state-declaration` or as a -key under an :ref:`ID-declaration`. - -.. _requisite-reference: +State Module declaration +------------------------ -Requisite reference -------------------- +Names the Salt state module (for example ``file``, ``pkg``, +``service``) that provides the function invoked for the state. -A single key dictionary. The key is the name of the referenced -:ref:`state-declaration` and the value is the ID of the referenced -:ref:`ID-declaration`. +Occurs in the key/identifier of the :ref:`state-declaration` dictionary +under an :ref:`ID declaration `. -Occurs as a single index in a :ref:`requisite-declaration` list. +Multiple state module declarations can be specified under the same +ID declaration but per ID each state module must be unique. .. _function-declaration: @@ -164,6 +114,8 @@ Function declaration The name of the function to call within the state. A state declaration can contain only a single function declaration. +Occurs in the :ref:`state-declaration` + For example, the following state declaration calls the :mod:`installed ` function in the ``pkg`` state module: @@ -172,33 +124,30 @@ For example, the following state declaration calls the :mod:`installed httpd: pkg.installed: [] -The function can be declared inline with the state as a shortcut. -The actual data structure is compiled to this form: +The function can be declared combined inline with the +:ref:`state-module-declaration` separated by a period `.` +as a short form dot notation. +The actual data structure is compiled to the long form shown below: .. code-block:: yaml httpd: pkg: - - installed - -Where the function is a string in the body of the state declaration. -Technically when the function is declared in dot notation the compiler -converts it to be a string in the state declaration list. Note that the -use of the first example more than once in an ID declaration is invalid -yaml. + - fun: installed -INVALID: +If no arguments need to be given to the function, the argument list can be +omitted and the state declaration can be given as a single string in short form: .. code-block:: yaml httpd: pkg.installed - service.running -When passing a function without arguments and another state declaration -within a single ID declaration, then the long or "standard" format -needs to be used since otherwise it does not represent a valid data -structure. +Note that this string short form cannot be more than once per ID declaration. +When passing a function without arguments and another state declaration within +a single ID declaration component, then an empty list or dictionary needs +to be specified as the arguments value since otherwise it does not represent +a valid data structure. VALID: @@ -206,24 +155,43 @@ VALID: httpd: pkg.installed: [] - service.running: [] + service.running: {} -Occurs as the only index in the :ref:`state-declaration` list. +INVALID: + +.. code-block:: yaml + + httpd: + pkg.installed + service.running .. _function-arg-declaration: Function arg declaration ------------------------ -A single key dictionary referencing a Python type which is to be passed -to the named :ref:`function-declaration` as a parameter. The type must -be the data type expected by the function. +An argument consisting of keyword and value which is to be passed to the named +:ref:`function-declaration` as a parameter. The type of each value must be +the data type expected by the function. +The function arguments can be specified as one dictionary or as a list with each +item as a dictionary containing a single keyword and value. Occurs under a :ref:`function-declaration`. -For example in the following state declaration ``user``, ``group``, and +For example, in the following state declaration ``user``, ``group``, and ``mode`` are passed as arguments to the :mod:`managed -` function in the ``file`` state module: +` function in the ``file`` state module by +specifying the arguments as a dictionary: + +.. code-block:: yaml + + /etc/http/conf/http.conf: + file.managed: + user: root + group: root + mode: '0644' + +In this example the arguments are specified as a list of single item dictionaries: .. code-block:: yaml @@ -231,20 +199,82 @@ For example in the following state declaration ``user``, ``group``, and file.managed: - user: root - group: root - - mode: 644 + - mode: '0644' + +.. _requisite-declaration: + +Requisite declaration +--------------------- + +A key value pair of a key that is a :ref:`requisite type ` +with a value that is a list containing :ref:`requisite references `. + +Used to build the action dependency tree. While Salt states are made to +execute in a deterministic order, this order is managed by requiring +and watching other Salt states. + +Occurs as a component in a :ref:`state-declaration`. + +.. code-block:: yaml + + : + - + - + +.. code-block:: yaml + + require: # requisite type + - file: /etc/http/conf/http.conf + - service: httpd + - httpd + +See requisites: :ref:`Requisites ` + +.. _requisite-type-declaration: + +Requisite type declaration +-------------------------- + +The type of the dependency/requisite relationship. + +Occurs in a :ref:`requisite-declaration`. + +See :ref:`requisite-types` + +.. _requisite-reference: + +Requisite reference +------------------- + +One of the items in a :ref:`requisite-declaration` list that specifies +a target of the requisite. + +Either + +- A key value pair where the key is the name of the referenced + :ref:`state-module-declaration` and the value is the ID of the referenced + :ref:`ID-declaration` or the :ref:`name ` of the + referenced :ref:`state-declaration`. + For example the reference `file: vim` is a reference a state declaration + with the to the state module ``file`` with the ID or name ``vim`` +- A single string identifier. In version 2016.3.0, the state module name was + made optional. If the state module is omitted, all states matching the + identifier will be required, regardless of which state module they are using. + +Occurs in a :ref:`requisite-declaration` list. .. _name-declaration: Name declaration ---------------- -Overrides the ``name`` argument of a :ref:`state-declaration`. If +Specifies the ``name`` argument of a :ref:`state-declaration`. If ``name`` is not specified the :ref:`ID-declaration` satisfies the ``name`` argument. -The name is always a single key dictionary referencing a string. +The name is a string. -Overriding ``name`` is useful for a variety of scenarios. +Including a ``name`` declaration is useful for a variety of scenarios. For example, avoiding clashing ID declarations. The following two state declarations cannot both have ``/etc/motd`` as the ID declaration: @@ -253,13 +283,13 @@ declarations cannot both have ``/etc/motd`` as the ID declaration: motd_perms: file.managed: - - name: /etc/motd - - mode: 644 + name: /etc/motd + mode: '0644' motd_quote: file.append: - - name: /etc/motd - - text: "Of all smells, bread; of all tastes, salt." + name: /etc/motd + text: "Of all smells, bread; of all tastes, salt." Another common reason to override ``name`` is if the ID declaration is long and needs to be referenced in multiple places. In the example below it is much @@ -270,18 +300,18 @@ easier to specify ``mywebsite`` than to specify mywebsite: file.managed: - - name: /etc/apache2/sites-available/mywebsite.com - - source: salt://mywebsite.com + name: /etc/apache2/sites-available/mywebsite.com + source: salt://mywebsite.com a2ensite mywebsite.com: cmd.wait: - - unless: test -L /etc/apache2/sites-enabled/mywebsite.com - - watch: + unless: test -L /etc/apache2/sites-enabled/mywebsite.com + watch: - file: mywebsite apache2: service.running: - - watch: + watch: - file: mywebsite .. _names-declaration: @@ -298,7 +328,7 @@ For example, given the following state declaration: python-pkgs: pkg.installed: - - names: + names: - python-django - python-crypto - python-yaml @@ -324,16 +354,29 @@ dictionary level. .. code-block:: yaml - ius: - pkgrepo.managed: - - humanname: IUS Community Packages for Enterprise Linux 6 - $basearch - - gpgcheck: 1 - - baseurl: http://mirror.rackspace.com/ius/stable/CentOS/6/$basearch - - gpgkey: http://dl.iuscommunity.org/pub/ius/IUS-COMMUNITY-GPG-KEY - - names: + ius: + pkgrepo.managed: + humanname: IUS Community Packages for Enterprise Linux 6 - $basearch + gpgcheck: 1 + baseurl: http://mirror.rackspace.com/ius/stable/CentOS/6/$basearch + gpgkey: http://dl.iuscommunity.org/pub/ius/IUS-COMMUNITY-GPG-KEY + names: - ius - ius-devel: - - baseurl: http://mirror.rackspace.com/ius/development/CentOS/6/$basearch + - baseurl: http://mirror.rackspace.com/ius/development/CentOS/6/$basearch + +.. _extend-declaration: + +Extend declaration +------------------ + +Extends a :ref:`state-declaration` from an included ``SLS module``. + +Occurs only in the top level and defines a dictionary. + +States cannot be extended more than once in a single state run. + +See extending states: :ref:`Extending External SLS Data ` .. _states-highstate-example: @@ -346,58 +389,63 @@ components. .. code-block:: yaml : - - - - + - + - : : [] + # inline short form dot notation for function declaration with dictionary + # for function arguments, names, and requisites + : + .: + + + + + + + + # inline short form dot notation for function declaration with list + # for function arguments, names, and requisites + : + .: + - + - + - + - + - + - - # standard declaration - + # multiple states for single id : - : - - - - - - - - - - : - - : - - - - - - - # inline function and names - + .: + s... + + s... + .: + s... + + s... + + # traditional declaration : - .: - - - - - - - - : - - - - - - - - : - - - - - - - # multiple states for single id + : + - + - s... + - + - s... + # traditional declaration with mutiple states for single id : - : - - - - - - : - - : - - - : - - - - - - : - - - - - - : - - + : + - + - s... + - + - s... + : + - + - s... + - + - s... diff --git a/doc/ref/states/index.rst b/doc/ref/states/index.rst index 83d6669474d2..c3c2596b99e0 100644 --- a/doc/ref/states/index.rst +++ b/doc/ref/states/index.rst @@ -119,23 +119,23 @@ Here is an example of a Salt State: salt: pkg.latest: - - name: salt + name: salt service.running: - - names: + names: - salt-master - salt-minion - - require: + require: - pkg: salt - - watch: + watch: - file: /etc/salt/minion /etc/salt/minion: file.managed: - - source: salt://salt/minion - - user: root - - group: root - - mode: 644 - - require: + source: salt://salt/minion + user: root + group: root + mode: 644 + require: - pkg: salt This short stanza will ensure that vim is installed, Salt is installed and up @@ -221,13 +221,13 @@ the following state file which we'll call ``pep8.sls``: python-pip: cmd.run: - - name: | + name: | easy_install --script-dir=/usr/bin -U pip - - cwd: / + cwd: / pep8: pip.installed: - - require: + require: - cmd: python-pip @@ -301,14 +301,14 @@ The modified state file would now be: python-pip: cmd.run: - - name: | + name: | easy_install --script-dir=/usr/bin -U pip - - cwd: / - - reload_modules: true + cwd: / + reload_modules: true pep8: pip.installed: - - require: + require: - cmd: python-pip diff --git a/doc/ref/states/ordering.rst b/doc/ref/states/ordering.rst index 856da2eb0b61..15aefb69b81b 100644 --- a/doc/ref/states/ordering.rst +++ b/doc/ref/states/ordering.rst @@ -62,9 +62,9 @@ These requisite statements are applied to a specific state declaration: httpd: pkg.installed: [] file.managed: - - name: /etc/httpd/conf/httpd.conf - - source: salt://httpd/httpd.conf - - require: + name: /etc/httpd/conf/httpd.conf + source: salt://httpd/httpd.conf + require: - pkg: httpd In this example, the **require** requisite is used to declare that the file @@ -78,7 +78,7 @@ can be evaluated to see if they have executed correctly. Require statements can refer to any state defined in Salt. The basic examples are `pkg`, `service`, and `file`, but any used state can be referenced. -In addition to state declarations such as pkg, file, etc., **sls** type requisites +In addition to state module declarations such as pkg, file, etc., **sls** type requisites are also recognized, and essentially allow 'chaining' of states. This provides a mechanism to ensure the proper sequence for complex state formulas, especially when the discrete states are split or groups into separate sls files: @@ -91,7 +91,7 @@ the discrete states are split or groups into separate sls files: httpd: pkg.installed: [] service.running: - - require: + require: - pkg: httpd - sls: network @@ -119,17 +119,17 @@ more requisites. Both requisite types can also be separately declared: httpd: pkg.installed: [] service.running: - - enable: True - - watch: + enable: True + watch: - file: /etc/httpd/conf/httpd.conf - - require: + require: - pkg: httpd - user: httpd - group: httpd file.managed: - - name: /etc/httpd/conf/httpd.conf - - source: salt://httpd/httpd.conf - - require: + name: /etc/httpd/conf/httpd.conf + source: salt://httpd/httpd.conf + require: - pkg: httpd user.present: [] group.present: [] @@ -160,7 +160,7 @@ with the option `order`: vim: pkg.installed: - - order: 1 + order: 1 By adding the order option to `1` this ensures that the vim package will be installed in tandem with any other state declaration set to the order `1`. @@ -176,4 +176,4 @@ a state to the end of the line. To do this, set the order to ``last``: vim: pkg.installed: - - order: last + order: last diff --git a/doc/ref/states/parallel.rst b/doc/ref/states/parallel.rst index 38a18e9f11b8..38c293610a80 100644 --- a/doc/ref/states/parallel.rst +++ b/doc/ref/states/parallel.rst @@ -10,7 +10,7 @@ option to your state declaration: nginx: service.running: - - parallel: True + parallel: True Now ``nginx`` will be started in a separate process from the normal state run and will therefore not block additional states. @@ -28,17 +28,17 @@ Given this example: sleep 10: cmd.run: - - parallel: True + parallel: True nginx: service.running: - - parallel: True - - require: + parallel: True + require: - cmd: sleep 10 sleep 5: cmd.run: - - parallel: True + parallel: True The ``sleep 10`` will be started first, then the state system will block on starting nginx until the ``sleep 10`` completes. Once nginx has been ensured to @@ -55,16 +55,16 @@ before the ``nginx`` state sleep 10: cmd.run: - - parallel: True + parallel: True sleep 5: cmd.run: - - parallel: True + parallel: True nginx: service.running: - - parallel: True - - require: + parallel: True + require: - cmd: sleep 10 Now both of the sleep calls will be started in parallel and ``nginx`` will still diff --git a/doc/ref/states/providers.rst b/doc/ref/states/providers.rst index 3c250f204033..5077442f741a 100644 --- a/doc/ref/states/providers.rst +++ b/doc/ref/states/providers.rst @@ -19,8 +19,8 @@ can revert to the default service module: httpd: service.running: - - enable: True - - provider: service + enable: True + provider: service In this instance, the basic :py:mod:`~salt.modules.service` module (which manages :program:`sysvinit`-based services) will replace the @@ -45,7 +45,7 @@ module can be used to provide certain functionality. emacs: pkg.installed: - - provider: + provider: - cmd: customcmd In this example, the state is being instructed to use a custom module to invoke diff --git a/doc/ref/states/requisites.rst b/doc/ref/states/requisites.rst index c879e85f910e..1741c83bca5e 100644 --- a/doc/ref/states/requisites.rst +++ b/doc/ref/states/requisites.rst @@ -9,8 +9,8 @@ Requisites The Salt requisite system is used to create relationships between states. This provides a method to easily define inter-dependencies between states. These -dependencies are expressed by declaring the relationships using state names -and IDs or names. The generalized form of a requisite target is ``: +dependencies are expressed by declaring the relationships using state module names +and IDs or names. The generalized form of a requisite target is ``: ``. The specific form is defined as a :ref:`Requisite Reference `. @@ -23,10 +23,10 @@ package could not be installed, Salt will not try to manage the service. nginx: pkg.installed: - - name: nginx-light + name: nginx-light service.running: - - enable: True - - require: + enable: True + require: - pkg: nginx Without the requisite defined, salt would attempt to install the package and @@ -54,9 +54,9 @@ Requisites typically need two pieces of information for matching: nginx: pkg.installed: [] file.managed: - - name: /etc/nginx/nginx.conf + name: /etc/nginx/nginx.conf service.running: - - require: + require: - pkg: nginx - file: /etc/nginx/nginx.conf @@ -75,13 +75,13 @@ so either of the following versions for "Extract server package" is correct: # Match by ID declaration Extract server package: archive.extracted: - - onchanges: + onchanges: - file: Deploy server package # Match by name parameter Extract server package: archive.extracted: - - onchanges: + onchanges: - file: /usr/local/share/myapp.tar.xz Wildcard matching in requisites @@ -105,7 +105,7 @@ will reload/restart the service: apache2: service.running: - - watch: + watch: - file: /etc/apache2/* A leading or bare ``*`` must be quoted to avoid confusion with YAML references: @@ -114,7 +114,7 @@ A leading or bare ``*`` must be quoted to avoid confusion with YAML references: /etc/letsencrypt/renewal-hooks/deploy/install.sh: cmd.run: - - onchanges: + onchanges: - acme: '*' @@ -129,9 +129,10 @@ module they are using. .. code-block:: yaml - - require: + require: - vim +.. _requisite-types: Requisites Types ---------------- @@ -178,7 +179,7 @@ In the following example, the ``service`` state will not be checked unless both nginx: service.running: - - require: + require: - file: /etc/nginx/nginx.conf - file: /etc/nginx/conf.d/ssl.conf @@ -196,7 +197,7 @@ file: bar: pkg.installed: - - require: + require: - sls: foo This will add a ``require`` to all of the state declarations found in the given @@ -222,11 +223,11 @@ if any of the watched states changes. myservice: file.managed: - - name: /etc/myservice/myservice.conf - - source: salt://myservice/files/myservice.conf + name: /etc/myservice/myservice.conf + source: salt://myservice/files/myservice.conf cmd.run: - - name: /usr/local/sbin/run-build - - onchanges: + name: /usr/local/sbin/run-build + onchanges: - file: /etc/myservice/myservice.conf In the example above, ``cmd.run`` will run only if there are changes in the @@ -239,11 +240,11 @@ correct choice, as seen in this next example. myservice: file.managed: - - name: /etc/myservice/myservice.conf - - source: salt://myservice/files/myservice.conf + name: /etc/myservice/myservice.conf + source: salt://myservice/files/myservice.conf cmd.run: - - name: /usr/local/sbin/run-build - - onchanges_in: # <-- broken logic + name: /usr/local/sbin/run-build + onchanges_in: # <-- broken logic - file: /etc/myservice/myservice.conf @@ -295,11 +296,11 @@ to Salt ensuring that the service is running. ntpd: service.running: - - watch: + watch: - file: /etc/ntp.conf file.managed: - - name: /etc/ntp.conf - - source: salt://ntp/files/ntp.conf + name: /etc/ntp.conf + source: salt://ntp/files/ntp.conf Another useful example of ``watch`` is using salt to ensure a configuration file is present and in a correct state, ensure the service is running, and trigger @@ -310,12 +311,12 @@ dropping any connections. nginx: service.running: - - reload: True - - watch: + reload: True + watch: - file: nginx file.managed: - - name: /etc/nginx/conf.d/tls-settings.conf - - source: salt://nginx/files/tls-settings.conf + name: /etc/nginx/conf.d/tls-settings.conf + source: salt://nginx/files/tls-settings.conf .. note:: @@ -391,14 +392,14 @@ every necessary change. You might be tempted to write something like this: httpd: service.running: - - enable: True - - watch: + enable: True + watch: - file: httpd-config httpd-config: file.managed: - - name: /etc/httpd/conf/httpd.conf - - source: salt://httpd/files/apache.conf + name: /etc/httpd/conf/httpd.conf + source: salt://httpd/files/apache.conf If your service is already running but not enabled, you might expect that Salt will be able to tell that since the config file changed your service needs to @@ -413,18 +414,18 @@ simply make sure that ``service.running`` is in a state on its own: enable-httpd: service.enabled: - - name: httpd + name: httpd start-httpd: service.running: - - name: httpd - - watch: + name: httpd + watch: - file: httpd-config httpd-config: file.managed: - - name: /etc/httpd/conf/httpd.conf - - source: salt://httpd/files/apache.conf + name: /etc/httpd/conf/httpd.conf + source: salt://httpd/files/apache.conf Now that ``service.running`` is its own state, changes to ``service.enabled`` will no longer prevent ``mod_watch`` from getting triggered, so your ``httpd`` @@ -445,30 +446,30 @@ created by ``listen`` will execute at the end of the state run. restart-apache2: service.running: - - name: apache2 - - listen: + name: apache2 + listen: - file: /etc/apache2/apache2.conf configure-apache2: file.managed: - - name: /etc/apache2/apache2.conf - - source: salt://apache2/apache2.conf + name: /etc/apache2/apache2.conf + source: salt://apache2/apache2.conf This example will cause apache2 to restart when the apache2.conf file is changed, but the apache2 restart will happen at the end of the state run. .. code-block:: yaml - restart-apache2: - service.running: - - name: apache2 + restart-apache2: + service.running: + name: apache2 - configure-apache2: - file.managed: - - name: /etc/apache2/apache2.conf - - source: salt://apache2/apache2.conf - - listen_in: - - service: apache2 + configure-apache2: + file.managed: + name: /etc/apache2/apache2.conf + source: salt://apache2/apache2.conf + listen_in: + - service: apache2 This example does the same as the above example, but puts the state argument on the file resource, rather than the service resource. @@ -494,14 +495,14 @@ is the pre-required state. graceful-down: cmd.run: - - name: service apache graceful - - prereq: + name: service apache graceful + prereq: - file: site-code site-code: file.recurse: - - name: /opt/site_code - - source: salt://site/code + name: /opt/site_code + source: salt://site/code In this case, the apache server will only be shut down if the site-code state expects to deploy fresh code via the file.recurse call. The site-code deployment @@ -542,28 +543,28 @@ The ``onfail`` requisite is applied in the same way as ``require`` and ``watch`` primary_mount: mount.mounted: - - name: /mnt/share - - device: 10.0.0.45:/share - - fstype: nfs + name: /mnt/share + device: 10.0.0.45:/share + fstype: nfs backup_mount: mount.mounted: - - name: /mnt/share - - device: 192.168.40.34:/share - - fstype: nfs - - onfail: + name: /mnt/share + device: 192.168.40.34:/share + fstype: nfs + onfail: - mount: primary_mount .. code-block:: yaml build_site: cmd.run: - - name: /srv/web/app/build_site + name: /srv/web/app/build_site notify-build_failure: hipchat.send_message: - - room_id: 123456 - - message: "Building website fail on {{ grains['id'] }}" + room_id: 123456 + message: "Building website fail on {{ grains['id'] }}" The default behavior of the ``onfail`` when multiple requisites are listed is @@ -577,17 +578,17 @@ form: test_site_a: cmd.run: - - name: ping -c1 10.0.0.1 + name: ping -c1 10.0.0.1 test_site_b: cmd.run: - - name: ping -c1 10.0.0.2 + name: ping -c1 10.0.0.2 notify_site_down: hipchat.send_message: - - room_id: 123456 - - message: "Both primary and backup sites are down!" - - onfail_all: + room_id: 123456 + message: "Both primary and backup sites are down!" + onfail_all: - cmd: test_site_a - cmd: test_site_b @@ -625,17 +626,17 @@ id declaration. This is useful when many files need to have the same defaults. /etc/foo.conf: file.managed: - - source: salt://foo.conf - - template: jinja - - mkdirs: True - - user: apache - - group: apache - - mode: 755 + source: salt://foo.conf + template: jinja + mkdirs: True + user: apache + group: apache + mode: 755 /etc/bar.conf: file.managed: - - source: salt://bar.conf - - use: + source: salt://bar.conf + use: - file: /etc/foo.conf The ``use`` statement was developed primarily for the networking states but @@ -669,14 +670,14 @@ the exact same dependency mapping. httpd: pkg.installed: [] service.running: - - require: + require: - pkg: httpd .. code-block:: yaml httpd: pkg.installed: - - require_in: + require_in: - service: httpd service.running: [] @@ -689,9 +690,9 @@ nginx`` requisite. nginx: pkg.installed: [] service.running: - - enable: True - - reload: True - - require: + enable: True + reload: True + require: - pkg: nginx php.sls @@ -703,7 +704,7 @@ php.sls php: pkg.installed: - - require_in: + require_in: - service: httpd mod_python.sls @@ -715,7 +716,7 @@ mod_python.sls mod_python: pkg.installed: - - require_in: + require_in: - service: httpd Now the httpd server will only start if both php and mod_python are first verified to @@ -726,10 +727,10 @@ be installed. Thus allowing for a requisite to be defined "after the fact". {% for cfile in salt['pillar.get']('nginx:config_files') %} /etc/nginx/conf.d/{{ cfile }}: file.managed: - - source: salt://nginx/configs/{{ cfile }} - - require: + source: salt://nginx/configs/{{ cfile }} + require: - pkg: nginx - - listen_in: + listen_in: - service: nginx {% endfor %} @@ -754,18 +755,18 @@ from ``all()`` to ``any()``. A: cmd.run: - - name: echo A - - require_any: + name: echo A + require_any: - cmd: B - cmd: C B: cmd.run: - - name: echo B + name: echo B C: cmd.run: - - name: /bin/false + name: /bin/false In this example ``A`` will run because at least one of the requirements specified, ``B`` or ``C``, will succeed. @@ -777,18 +778,18 @@ In this example ``A`` will run because at least one of the requirements specifie /etc/myservice/myservice.conf: file.managed: - - source: salt://myservice/files/myservice.conf + source: salt://myservice/files/myservice.conf /etc/yourservice/yourservice.conf: file.managed: - - source: salt://yourservice/files/yourservice.conf + source: salt://yourservice/files/yourservice.conf /usr/local/sbin/myservice/post-changes-hook.sh cmd.run: - - onchanges_any: + onchanges_any: - file: /etc/myservice/myservice.conf - file: /etc/your_service/yourservice.conf - - require: + require: - pkg: myservice In this example, `cmd.run` would be run only if either of the `file.managed` @@ -817,12 +818,12 @@ See :ref:`Reloading Modules `. grains_refresh: module.run: - - name: saltutil.refresh_grains - - reload_grains: true + name: saltutil.refresh_grains + reload_grains: true grains_read: module.run: - - name: grains.items + name: grains.items .. _unless-requisite: @@ -847,7 +848,7 @@ concept of ``True`` and ``False``. vim: pkg.installed: - - unless: + unless: - rpm -q vim-enhanced - ls /usr/bin/vim @@ -866,10 +867,10 @@ For example: deploy_app: cmd.run: - - names: + names: - first_deploy_cmd - second_deploy_cmd - - unless: some_check + unless: some_check In the above case, ``some_check`` will be run prior to _each_ name -- once for ``first_deploy_cmd`` and a second time for ``second_deploy_cmd``. @@ -884,9 +885,9 @@ In the above case, ``some_check`` will be run prior to _each_ name -- once for install apache on debian based distros: cmd.run: - - name: make install - - cwd: /path/to/dir/whatever-2.1.5/ - - unless: + name: make install + cwd: /path/to/dir/whatever-2.1.5/ + unless: - fun: file.file_exists path: /usr/local/bin/whatever @@ -894,10 +895,10 @@ In the above case, ``some_check`` will be run prior to _each_ name -- once for set mysql root password: debconf.set: - - name: mysql-server-5.7 - - data: + name: mysql-server-5.7 + data: 'mysql-server/root_password': {'type': 'password', 'value': {{pillar['mysql.pass']}} } - - unless: + unless: - fun: pkg.version args: - mysql-server-5.7 @@ -910,8 +911,8 @@ In the above case, ``some_check`` will be run prior to _each_ name -- once for test: test.nop: - - name: foo - - unless: + name: foo + unless: - fun: consul.get consul_url: http://127.0.0.1:8500 key: not-existing @@ -933,11 +934,11 @@ In the above case, ``some_check`` will be run prior to _each_ name -- once for jim_nologin: user.present: - - name: jim - - shell: /sbin/nologin - - unless: + name: jim + shell: /sbin/nologin + unless: - echo hello world - - cmd_opts_exclude: + cmd_opts_exclude: - shell .. _onlyif-requisite: @@ -962,19 +963,19 @@ concept of ``True`` and ``False``. stop-volume: module.run: - - name: glusterfs.stop_volume - - m_name: work - - onlyif: + name: glusterfs.stop_volume + m_name: work + onlyif: - gluster volume status work - - order: 1 + order: 1 remove-volume: module.run: - - name: glusterfs.delete - - m_name: work - - onlyif: + name: glusterfs.delete + m_name: work + onlyif: - gluster volume info work - - watch: + watch: - cmd: stop-volume The above example ensures that the stop_volume and delete modules only run @@ -990,15 +991,15 @@ if the gluster commands return a 0 ret value. install apache on redhat based distros: pkg.latest: - - name: httpd - - onlyif: + name: httpd + onlyif: - fun: match.grain tgt: 'os_family:RedHat' install apache on debian based distros: pkg.latest: - - name: apache2 - - onlyif: + name: apache2 + onlyif: - fun: match.grain tgt: 'os_family:Debian' @@ -1006,8 +1007,8 @@ if the gluster commands return a 0 ret value. arbitrary file example: file.touch: - - name: /path/to/file - - onlyif: + name: /path/to/file + onlyif: - fun: file.search args: - /etc/crontab @@ -1021,8 +1022,8 @@ if the gluster commands return a 0 ret value. test: test.nop: - - name: foo - - onlyif: + name: foo + onlyif: - fun: consul.get consul_url: http://127.0.0.1:8500 key: does-exist @@ -1044,11 +1045,11 @@ if the gluster commands return a 0 ret value. jim_nologin: user.present: - - name: jim - - shell: /sbin/nologin - - onlyif: + name: jim + shell: /sbin/nologin + onlyif: - echo hello world - - cmd_opts_exclude: + cmd_opts_exclude: - shell .. _creates-requisite: @@ -1068,8 +1069,8 @@ should execute. This was previously used by the :mod:`cmd ` and contrived creates example: file.touch: - - name: /path/to/file - - creates: /path/to/file + name: /path/to/file + creates: /path/to/file ``creates`` also accepts a list of files, in which case this state will run if **any** of the files do not exist: @@ -1078,8 +1079,8 @@ run if **any** of the files do not exist: creates list: file.cmd: - - name: /path/to/command - - creates: + name: /path/to/command + creates: - /path/file - /path/file2 @@ -1096,9 +1097,9 @@ the command in the ``cmd.run`` module. django: pip.installed: - - name: django >= 1.6, <= 1.7 - - runas: daniel - - require: + name: django >= 1.6, <= 1.7 + runas: daniel + require: - pkg: python-pip In the above state, the pip command run by ``cmd.run`` will be run by the daniel user. @@ -1116,9 +1117,9 @@ is specified. It will be set when ``runas_password`` is defined in the state. run_script: cmd.run: - - name: Powershell -NonInteractive -ExecutionPolicy Bypass -File C:\\Temp\\script.ps1 - - runas: frank - - runas_password: supersecret + name: Powershell -NonInteractive -ExecutionPolicy Bypass -File C:\\Temp\\script.ps1 + runas: frank + runas_password: supersecret In the above state, the Powershell script run by ``cmd.run`` will be run by the frank user with the password ``supersecret``. @@ -1140,10 +1141,10 @@ the salt-minion. comment-repo: file.replace: - - name: /etc/yum.repos.d/fedora.repo - - pattern: '^enabled=0' - - repl: enabled=1 - - check_cmd: + name: /etc/yum.repos.d/fedora.repo + pattern: '^enabled=0' + repl: enabled=1 + check_cmd: - "! grep 'enabled=0' /etc/yum.repos.d/fedora.repo" This will attempt to do a replace on all ``enabled=0`` in the .repo file, and @@ -1188,8 +1189,8 @@ of the state gets added to the tag. nano_stuff: pkg.installed: - - name: nano - - fire_event: True + name: nano + fire_event: True In the following example instead of setting `fire_event` to `True`, `fire_event` is set to an arbitrary string, which will cause the event to be @@ -1200,8 +1201,8 @@ sent with this tag: nano_stuff: pkg.installed: - - name: nano - - fire_event: custom/tag/nano/finished + name: nano + fire_event: custom/tag/nano/finished Retrying States =============== @@ -1237,8 +1238,8 @@ up to an additional ``10`` seconds: my_retried_state: pkg.installed: - - name: nano - - retry: + name: nano + retry: attempts: 5 until: True interval: 60 @@ -1252,8 +1253,8 @@ returns ``True``. install_nano: pkg.installed: - - name: nano - - retry: True + name: nano + retry: True The following example will run the file.exists state every ``30`` seconds up to ``15`` times or until the file exists (i.e. the state returns ``True``). @@ -1262,8 +1263,8 @@ or until the file exists (i.e. the state returns ``True``). wait_for_file: file.exists: - - name: /path/to/file - - retry: + name: /path/to/file + retry: attempts: 15 interval: 30 @@ -1288,8 +1289,8 @@ For example: wait_for_file: file.exists: - - name: /path/to/file - - retry: + name: /path/to/file + retry: attempts: 10 interval: 2 splay: 5 @@ -1330,7 +1331,7 @@ states, but it is now a global state argument that can be applied to any state. cleanup_script: cmd.script: - - name: salt://myapp/files/my_script.sh - - umask: "077" - - onchanges: + name: salt://myapp/files/my_script.sh + umask: "077" + onchanges: - file: /some/file diff --git a/doc/ref/states/writing.rst b/doc/ref/states/writing.rst index 1e9c8a0c523b..fc897a85104a 100644 --- a/doc/ref/states/writing.rst +++ b/doc/ref/states/writing.rst @@ -22,10 +22,10 @@ illustrate: /etc/salt/master: # maps to "name", unless a "name" argument is specified below file.managed: # maps to . - e.g. "managed" in https://github.com/saltstack/salt/tree/|repo_primary_branch|/salt/states/file.py - - user: root # one of many options passed to the manage function - - group: root - - mode: 644 - - source: salt://salt/master + user: root # one of many options passed to the manage function + group: root + mode: 644 + source: salt://salt/master Therefore this SLS data can be directly linked to a module, function, and arguments passed to that function. diff --git a/doc/topics/best_practices.rst b/doc/topics/best_practices.rst index 154cc2ad58c5..3e2f2411439e 100644 --- a/doc/topics/best_practices.rst +++ b/doc/topics/best_practices.rst @@ -220,11 +220,11 @@ preferred: apache_conf: file.managed: - - name: {{ name }} - - source: {{ tmpl }} - - template: jinja - - user: root - - watch_in: + name: {{ name }} + source: {{ tmpl }} + template: jinja + user: root + watch_in: - service: apache @@ -253,11 +253,11 @@ locations within a single state: apache_conf: file.managed: - - name: {{ salt['pillar.get']('apache:lookup:name') }} - - source: {{ salt['pillar.get']('apache:lookup:config:tmpl') }} - - template: jinja - - user: root - - watch_in: + name: {{ salt['pillar.get']('apache:lookup:name') }} + source: {{ salt['pillar.get']('apache:lookup:config:tmpl') }} + template: jinja + user: root + watch_in: - service: apache This flexibility provides users with a centralized location to modify @@ -280,13 +280,13 @@ is not very modular to one that is: pkg: - installed service.running: - - enable: True + enable: True /etc/httpd/httpd.conf: file.managed: - - source: salt://apache/files/httpd.conf - - template: jinja - - watch_in: + source: salt://apache/files/httpd.conf + template: jinja + watch_in: - service: httpd The example above is probably the worst-case scenario when writing a state. @@ -303,7 +303,7 @@ There is also the issue of having the configuration file located in the init, as a user would be unable to simply install the service and use the default conf file. -Our second revision begins to address the referencing by using ``- name``, as +Our second revision begins to address the referencing by using ``name``, as opposed to direct ID references: ``/srv/salt/apache/init.sls``: @@ -312,17 +312,17 @@ opposed to direct ID references: apache: pkg.installed: - - name: httpd + name: httpd service.running: - - name: httpd - - enable: True + name: httpd + enable: True apache_conf: file.managed: - - name: /etc/httpd/httpd.conf - - source: salt://apache/files/httpd.conf - - template: jinja - - watch_in: + name: /etc/httpd/httpd.conf + source: salt://apache/files/httpd.conf + template: jinja + watch_in: - service: apache The above init file is better than our original, yet it has several issues @@ -374,18 +374,18 @@ modification of static values: apache: pkg.installed: - - name: {{ apache.server }} + name: {{ apache.server }} service.running: - - name: {{ apache.service }} - - enable: True + name: {{ apache.service }} + enable: True apache_conf: file.managed: - - name: {{ apache.conf }} - - source: {{ salt['pillar.get']('apache:lookup:config:tmpl') }} - - template: jinja - - user: root - - watch_in: + name: {{ apache.conf }} + source: {{ salt['pillar.get']('apache:lookup:config:tmpl') }} + template: jinja + user: root + watch_in: - service: apache The changes to this state now allow us to easily identify the location of the @@ -431,10 +431,10 @@ to be broken into two states. apache: pkg.installed: - - name: {{ apache.server }} + name: {{ apache.server }} service.running: - - name: {{ apache.service }} - - enable: True + name: {{ apache.service }} + enable: True ``/srv/salt/apache/conf.sls``: @@ -447,11 +447,11 @@ to be broken into two states. apache_conf: file.managed: - - name: {{ apache.conf }} - - source: {{ salt['pillar.get']('apache:lookup:config:tmpl') }} - - template: jinja - - user: root - - watch_in: + name: {{ apache.conf }} + source: {{ salt['pillar.get']('apache:lookup:config:tmpl') }} + template: jinja + user: root + watch_in: - service: apache This new structure now allows users to choose whether they only wish to @@ -480,7 +480,7 @@ accessible by the appropriate hosts: testdb: mysql_database.present: - - name: testerdb + name: testerdb ``/srv/salt/mysql/user.sls``: @@ -491,10 +491,10 @@ accessible by the appropriate hosts: testdb_user: mysql_user.present: - - name: frank - - password: "test3rdb" - - host: localhost - - require: + name: frank + password: "test3rdb" + host: localhost + require: - sls: mysql.testerdb Many users would review this state and see that the password is there in plain @@ -536,7 +536,7 @@ the associated pillar: testdb: mysql_database.present: - - name: {{ salt['pillar.get']('mysql:lookup:name') }} + name: {{ salt['pillar.get']('mysql:lookup:name') }} ``/srv/salt/mysql/user.sls``: @@ -547,10 +547,10 @@ the associated pillar: testdb_user: mysql_user.present: - - name: {{ salt['pillar.get']('mysql:lookup:user') }} - - password: {{ salt['pillar.get']('mysql:lookup:password') }} - - host: {{ salt['pillar.get']('mysql:lookup:host') }} - - require: + name: {{ salt['pillar.get']('mysql:lookup:user') }} + password: {{ salt['pillar.get']('mysql:lookup:password') }} + host: {{ salt['pillar.get']('mysql:lookup:host') }} + require: - sls: mysql.testerdb Now that the database details have been moved to the associated pillar file, diff --git a/doc/topics/cloud/salt.rst b/doc/topics/cloud/salt.rst index 15499cc59a97..5d9d641fec73 100644 --- a/doc/topics/cloud/salt.rst +++ b/doc/topics/cloud/salt.rst @@ -238,12 +238,12 @@ presence of the instance will be managed statefully. my-instance-name: cloud.present: - - cloud_provider: my-ec2-config - - image: ami-1624987f - - size: 't1.micro' - - ssh_username: ec2-user - - securitygroup: default - - delvol_on_destroy: True + cloud_provider: my-ec2-config + image: ami-1624987f + size: 't1.micro' + ssh_username: ec2-user + securitygroup: default + delvol_on_destroy: True cloud.profile ~~~~~~~~~~~~~ @@ -256,7 +256,7 @@ instance will be managed statefully. my-instance-name: cloud.profile: - - profile: ec2-centos64-x64 + profile: ec2-centos64-x64 cloud.absent ~~~~~~~~~~~~ diff --git a/doc/topics/development/conventions/formulas.rst b/doc/topics/development/conventions/formulas.rst index f5a8c8f1115a..644dc9d89b02 100644 --- a/doc/topics/development/conventions/formulas.rst +++ b/doc/topics/development/conventions/formulas.rst @@ -126,7 +126,7 @@ package until after the EPEL repository has also been installed: python26: pkg.installed: - - require: + require: - pkg: epel Including a Formula from a Top File @@ -257,8 +257,8 @@ file. This section contains several suggestions and examples. deploy_myapp: git.latest: - - name: git@github.com/myco/myapp.git - - version: {{ salt['pillar.get']('myapp:version', 'master') }} + name: git@github.com/myco/myapp.git + version: {{ salt['pillar.get']('myapp:version', 'master') }} Use a descriptive State ID `````````````````````````` @@ -285,7 +285,7 @@ Mine, the Scheduler, as well as with the CLI. # Do apache: pkg.installed: - - name: httpd + name: httpd # Don't apache: @@ -332,9 +332,9 @@ concepts or actions. # This template is fetched from a third-party and does not fit our # company norm of using Jinja. This must be processed using Mako. file.managed: - - name: /path/to/file.cfg - - source: salt://path/to/file.cfg.template - - template: mako + name: /path/to/file.cfg + source: salt://path/to/file.cfg.template + template: mako # Provide a description or explanation that did not fit within the state # ID. For example: @@ -346,10 +346,10 @@ concepts or actions. # FIXME: Joe needs this to run on Windows by next quarter. Switch these # from shell commands to Salt's file.managed and file.replace state # modules. - - name: | + name: | touch /path/to/file_last_updated sed -e 's/foo/bar/g' /path/to/file_environment - - onchanges: + onchanges: - file: a_config_file Be careful to use Jinja comments for commenting Jinja code and YAML comments @@ -479,8 +479,8 @@ Below is a simple example of a readable loop: {# Ensure unique state IDs when looping. #} {{ user.name }}-{{ loop.index }}: user.present: - - name: {{ user.name }} - - shell: {{ user.shell }} + name: {{ user.name }} + shell: {{ user.shell }} {% endfor %} @@ -496,9 +496,9 @@ both useful techniques to avoid this. For example: apache: pkg.installed: {% if grains.os_family == 'RedHat' %} - - name: httpd + name: httpd {% elif grains.os_family == 'Debian' %} - - name: apache2 + name: apache2 {% endif %} {# ---- Better example ---- #} @@ -511,7 +511,7 @@ both useful techniques to avoid this. For example: apache: pkg.installed: - - name: {{ name }} + name: {{ name }} {# ---- Good example ---- #} @@ -522,7 +522,7 @@ both useful techniques to avoid this. For example: apache: pkg.installed: - - name: {{ name }} + name: {{ name }} Dictionaries are useful to effectively "namespace" a collection of variables. This is useful with parametrization (discussed below). Dictionaries are also @@ -536,14 +536,14 @@ example: haproxy_conf: file.managed: - - name: /etc/haproxy/haproxy.cfg - - template: jinja + name: /etc/haproxy/haproxy.cfg + template: jinja {% if 'external_loadbalancer' in grains.roles %} - - source: salt://haproxy/external_haproxy.cfg + source: salt://haproxy/external_haproxy.cfg {% elif 'internal_loadbalancer' in grains.roles %} - - source: salt://haproxy/internal_haproxy.cfg + source: salt://haproxy/internal_haproxy.cfg {% endif %} - - context: + context: {% if 'external_loadbalancer' in grains.roles %} ssl_termination: True {% elif 'internal_loadbalancer' in grains.roles %} @@ -578,10 +578,10 @@ example: haproxy_conf: file.managed: - - name: /etc/haproxy/haproxy.cfg - - template: jinja - - source: {{ haproxy.source }} - - context: {{ haproxy.settings | yaml() }} + name: /etc/haproxy/haproxy.cfg + template: jinja + source: {{ haproxy.source }} + context: {{ haproxy.settings | yaml() }} There is still room for improvement in the above example. For example, extracting into an external file or replacing the if-elif conditional with a @@ -624,7 +624,7 @@ example is a state tree of two sls files, one simple and one complicated. common_users: user.present: - - names: + names: - larry - curly - moe @@ -681,9 +681,9 @@ Macros are useful for creating reusable, parameterized states. For example: {% macro user_state(state_id, user_name, shell='/bin/bash', groups=[]) %} {{ state_id }}: user.present: - - name: {{ user_name }} - - shell: {{ shell }} - - groups: {{ groups | json() }} + name: {{ user_name }} + shell: {{ shell }} + groups: {{ groups | json() }} {% endmacro %} {% for user_info in salt['pillar.get']('my_users', []) %} @@ -700,10 +700,10 @@ example, the following macro could be used to write a php.ini config file: php_ini: file.managed: - - name: /etc/php.ini - - source: salt://php.ini.tmpl - - template: jinja - - context: + name: /etc/php.ini + source: salt://php.ini.tmpl + template: jinja + context: php_ini_settings: {{ salt['pillar.get']('php_ini', {}) | json() }} ``/srv/pillar/php.sls``: @@ -799,9 +799,9 @@ state file using the following syntax: mysql-server: pkg.installed: - - name: {{ mysql.server }} + name: {{ mysql.server }} service.running: - - name: {{ mysql.service }} + name: {{ mysql.service }} Organizing Pillar data `````````````````````` @@ -998,9 +998,9 @@ XML.) appX_server_xml: file.serialize: - - name: /etc/tomcat/server.xml - - dataset: {{ server_xml_final_values | json() }} - - formatter: xml_badgerfish + name: /etc/tomcat/server.xml + dataset: {{ server_xml_final_values | json() }} + formatter: xml_badgerfish The :py:func:`file.serialize ` state can provide a shorthand for creating some files from data structures. There are also many @@ -1037,15 +1037,15 @@ example: deploy_application: git.latest: - - name: {{ app.repo_url }} - - version: {{ app.version }} - - target: {{ app.deploy_dir }} + name: {{ app.repo_url }} + version: {{ app.version }} + target: {{ app.deploy_dir }} myco/myapp/deployed: event.send: - - data: + data: version: {{ app.version }} - - onchanges: + onchanges: - git: deploy_application ``/srv/salt/app/defaults.yaml``: @@ -1097,7 +1097,7 @@ skips platform-specific options for brevity. See the full mod_wsgi: pkg.installed: [...] - - require: + require: - pkg: apache # apache/conf.sls @@ -1107,7 +1107,7 @@ skips platform-specific options for brevity. See the full apache_conf: file.managed: [...] - - watch_in: + watch_in: - service: apache To illustrate a bad example, say the above Apache formula installed Apache and @@ -1166,8 +1166,8 @@ the same. {% for user in list_list %} {{ user.name }}: user.present: - - name: {{ user.name }} - - shell: {{ user.shell }} + name: {{ user.name }} + shell: {{ user.shell }} {% endfor %} Configuration @@ -1197,9 +1197,9 @@ thousands of function calls across a large state tree. mod_status: file.managed: - - name: {{ apache.conf_dir }} - - source: {{ settings.get('mod_status_conf', 'salt://apache/mod_status.conf') }} - - template: {{ settings.get('template_engine', 'jinja') }} + name: {{ apache.conf_dir }} + source: {{ settings.get('mod_status_conf', 'salt://apache/mod_status.conf') }} + template: {{ settings.get('template_engine', 'jinja') }} Any default values used in the Formula must also be documented in the :file:`pillar.example` file in the root of the repository. Comments should be diff --git a/doc/topics/jinja/index.rst b/doc/topics/jinja/index.rst index 5cf422c3ef4d..2796e66f00b4 100644 --- a/doc/topics/jinja/index.rst +++ b/doc/topics/jinja/index.rst @@ -30,11 +30,11 @@ wrap conditional or redundant state elements: motd: file.managed: {% if grains['os'] == 'FreeBSD' %} - - name: /etc/motd + name: /etc/motd {% elif grains['os'] == 'Debian' %} - - name: /etc/motd.tail + name: /etc/motd.tail {% endif %} - - source: salt://motd + source: salt://motd In this example, the first **if** block will only be evaluated on minions that aren't running FreeBSD, and the second block changes the file name based on the @@ -54,7 +54,7 @@ defined variable might be easier: {% for motdfile in motd %} {{ motdfile }}: file.managed: - - source: salt://motd + source: salt://motd {% endfor %} Using a variable set by the template, the `for loop`_ will iterate over the @@ -125,11 +125,11 @@ contained blocks, may be necessary to emulate a variable return from the macro. python-virtualenv: pkg.installed: - - name: {{ pythonpkg('virtualenv') }} + name: {{ pythonpkg('virtualenv') }} python-fabric: pkg.installed: - - name: {{ pythonpkg('fabric') }} + name: {{ pythonpkg('fabric') }} .. code-block:: jinja @@ -311,9 +311,9 @@ Returns: thing: test.configurable_test_state: - - name: thing - - changes: true - - warnings: OMG! Stuff is happening! + name: thing + changes: true + warnings: OMG! Stuff is happening! .. jinja_ref:: to_bool @@ -2441,9 +2441,9 @@ Jinja_ can be used in the same way in managed files: # redis.sls /etc/redis/redis.conf: file.managed: - - source: salt://redis.conf - - template: jinja - - context: + source: salt://redis.conf + template: jinja + context: bind: 127.0.0.1 .. code-block:: jinja @@ -2601,7 +2601,7 @@ log at the ``profile`` level along with the render time of the block. test: cmd.run: - - name: |- + name: |- printf 'data: %s' '{{ local_data['counter'] }}' The ``profile`` block in the ``example.sls`` state will emit the following log @@ -2638,7 +2638,7 @@ Using the same logic as the ``profile`` block, the ``import_yaml``, test: cmd.run: - - name: |- + name: |- printf 'data: %s' '{{ imported['data'] }}' For ``import_*`` blocks, the ``profile`` log statement has the following form: diff --git a/doc/topics/network_automation/index.rst b/doc/topics/network_automation/index.rst index f57618b374a8..1287623aa484 100644 --- a/doc/topics/network_automation/index.rst +++ b/doc/topics/network_automation/index.rst @@ -401,9 +401,9 @@ Define the SLS state file, making use of the ntp_config_example: netconfig.managed: - - template_name: salt://ntp.jinja - - peers: {{ pillar.get('ntp.peers', []) | json }} - - servers: {{ pillar.get('ntp.servers', []) | json }} + template_name: salt://ntp.jinja + peers: {{ pillar.get('ntp.peers', []) | json }} + servers: {{ pillar.get('ntp.servers', []) | json }} Run the state and assure NTP configuration consistency across your multi-vendor network: diff --git a/doc/topics/salt_system_architecture.rst b/doc/topics/salt_system_architecture.rst index cc755f7a5b1b..5e104d45a3ad 100644 --- a/doc/topics/salt_system_architecture.rst +++ b/doc/topics/salt_system_architecture.rst @@ -154,7 +154,7 @@ example: install_vim_now: pkg.installed: - - pkgs: + pkgs: - vim To apply this state to a minion, you would use the ``state.apply`` module, such diff --git a/doc/topics/slots/index.rst b/doc/topics/slots/index.rst index 3259a489ae54..edd871ccae4b 100644 --- a/doc/topics/slots/index.rst +++ b/doc/topics/slots/index.rst @@ -52,8 +52,8 @@ Here is a simple example: copy-some-file: file.copy: - - name: __slot__:salt:test.echo(text=/tmp/some_file) - - source: __slot__:salt:test.echo(/etc/hosts) + name: __slot__:salt:test.echo(text=/tmp/some_file) + source: __slot__:salt:test.echo(/etc/hosts) This will execute the :py:func:`test.echo ` execution functions right before calling the state. The functions in the example will @@ -66,8 +66,8 @@ Here is an example of result parsing and appending: file-in-user-home: file.copy: - - name: __slot__:salt:user.info(someuser).home ~ /subdirectory - - source: salt://somefile + name: __slot__:salt:user.info(someuser).home ~ /subdirectory + source: salt://somefile Example Usage ------------- @@ -86,14 +86,14 @@ modified during the Salt run. add_group_known_users: group.present: - - name: known_users + name: known_users add_user: user.present: - - name: foobar - - uid: 600 - - gid: __slot__:salt:group.info("known_users").gid - - require: + name: foobar + uid: 600 + gid: __slot__:salt:group.info("known_users").gid + require: - group: add_group_known_users In this example, the ``add_group_known_users`` state ensures the presence of the @@ -132,8 +132,8 @@ text "hello world." This output is then used as the content of the file content-from-slots: file.managed: - - name: /tmp/things.txt - - contents: __slot__:salt:test.echo("hello world") + name: /tmp/things.txt + contents: __slot__:salt:test.echo("hello world") **Example 2: Using Multiple `test.echo` Outputs as Appended Content** @@ -145,8 +145,8 @@ and then used as the content of the file `/tmp/things.txt`: content-from-multiple-slots: file.managed: - - name: /tmp/things.txt - - contents: + name: /tmp/things.txt + contents: - __slot__:salt:test.echo("hello") - __slot__:salt:test.echo("world") @@ -167,9 +167,9 @@ the file `/tmp/grains.json`: serialize-dataset-from-slots: file.serialize: - - name: /tmp/grains.json - - serializer: json - - dataset: __slot__:salt:grains.items() + name: /tmp/grains.json + serializer: json + dataset: __slot__:salt:grains.items() These examples showcase how to leverage Salt's flexibility to use execution module returns as file contents or serialized data in your Salt states, allowing diff --git a/doc/topics/states/index.rst b/doc/topics/states/index.rst index 0ab336fa2d84..be48745c837b 100644 --- a/doc/topics/states/index.rst +++ b/doc/topics/states/index.rst @@ -38,8 +38,8 @@ resources to learn more about state and renderers. moe: user.rename: - - new_name: larry - - onlyif: id moe + new_name: larry + onlyif: id moe You must use the :mod:`module ` states to call execution modules directly. Here's an example: @@ -48,10 +48,10 @@ resources to learn more about state and renderers. rename_moe: module.run: - - name: user.rename - - m_name: moe - - new_name: larry - - onlyif: id moe + name: user.rename + m_name: moe + new_name: larry + onlyif: id moe **Renderers** Renderers use state configuration files written in a variety of languages, diff --git a/doc/topics/troubleshooting/yaml_idiosyncrasies.rst b/doc/topics/troubleshooting/yaml_idiosyncrasies.rst index 1ee1f5326f21..6f17603cf447 100644 --- a/doc/topics/troubleshooting/yaml_idiosyncrasies.rst +++ b/doc/topics/troubleshooting/yaml_idiosyncrasies.rst @@ -121,7 +121,7 @@ example the following state: /tmp/foo.txt: file.managed: - - contents: | + contents: | foo bar baz @@ -139,7 +139,7 @@ run the state: --- /tmp/foo.txt: file.managed: - - contents: | + contents: | foo bar <====================== baz @@ -152,7 +152,7 @@ The correct indentation would be as follows: /tmp/foo.txt: file.managed: - - contents: | + contents: | foo bar baz @@ -193,9 +193,9 @@ string literal: cheese: ssh_auth.present: - - user: tbortels - - source: salt://ssh_keys/chease.pub - - config: '%h/.ssh/authorized_keys' + user: tbortels + source: salt://ssh_keys/chease.pub + config: '%h/.ssh/authorized_keys' Time Expressions ================ @@ -270,10 +270,10 @@ ALSO DOES NOT WORK: fred: user.present ssh_auth.present: - - name: AAAAB3NzaC... - - user: fred - - enc: ssh-dss - - require: + name: AAAAB3NzaC... + user: fred + enc: ssh-dss + require: - user: fred The correct way is to define them like this: @@ -287,10 +287,10 @@ The correct way is to define them like this: fred: user.present: [] ssh_auth.present: - - name: AAAAB3NzaC... - - user: fred - - enc: ssh-dss - - require: + name: AAAAB3NzaC... + user: fred + enc: ssh-dss + require: - user: fred diff --git a/doc/topics/tutorials/cloud_controller.rst b/doc/topics/tutorials/cloud_controller.rst index 3f42a2204bbf..6354e8e9c751 100644 --- a/doc/topics/tutorials/cloud_controller.rst +++ b/doc/topics/tutorials/cloud_controller.rst @@ -60,20 +60,20 @@ to set up the libvirt pki keys. libvirt: pkg.installed: [] file.managed: - - name: /etc/sysconfig/libvirtd - - contents: 'LIBVIRTD_ARGS="--listen"' - - require: + name: /etc/sysconfig/libvirtd + contents: 'LIBVIRTD_ARGS="--listen"' + require: - pkg: libvirt virt.keys: - - require: + require: - pkg: libvirt service.running: - - name: libvirtd - - require: + name: libvirtd + require: - pkg: libvirt - network: br0 - libvirt: libvirt - - watch: + watch: - file: libvirt libvirt-python: @@ -81,7 +81,7 @@ to set up the libvirt pki keys. libguestfs: pkg.installed: - - pkgs: + pkgs: - libguestfs - libguestfs-tools @@ -96,16 +96,16 @@ a hypervisor connecting the bridge to eth0: eth0: network.managed: - - enabled: True - - type: eth - - bridge: br0 + enabled: True + type: eth + bridge: br0 br0: network.managed: - - enabled: True - - type: bridge - - proto: dhcp - - require: + enabled: True + type: bridge + proto: dhcp + require: - network: eth0 diff --git a/doc/topics/tutorials/esxi_proxy_minion.rst b/doc/topics/tutorials/esxi_proxy_minion.rst index 4a09b652a269..f00863d946a0 100644 --- a/doc/topics/tutorials/esxi_proxy_minion.rst +++ b/doc/topics/tutorials/esxi_proxy_minion.rst @@ -464,48 +464,48 @@ syslog, ntp, enabling VMotion, resetting a host password, and more. configure-host-ssh: esxi.ssh_configured: - - service_running: True - - ssh_key_file: /etc/salt/ssh_keys/my_key.pub - - service_policy: 'automatic' - - service_restart: True - - certificate_verify: True + service_running: True + ssh_key_file: /etc/salt/ssh_keys/my_key.pub + service_policy: 'automatic' + service_restart: True + certificate_verify: True configure-host-coredump: esxi.coredump_configured: - - enabled: True - - dump_ip: 'my-coredump-ip.example.com' + enabled: True + dump_ip: 'my-coredump-ip.example.com' configure-host-syslog: esxi.syslog_configured: - - syslog_configs: + syslog_configs: loghost: ssl://localhost:5432,tcp://10.1.0.1:1514 default-timeout: 120 - - firewall: True - - reset_service: True - - reset_syslog_config: True - - reset_configs: loghost,default-timeout + firewall: True + reset_service: True + reset_syslog_config: True + reset_configs: loghost,default-timeout configure-host-ntp: esxi.ntp_configured: - - service_running: True - - ntp_servers: + service_running: True + ntp_servers: - 192.174.1.100 - 192.174.1.200 - - service_policy: 'automatic' - - service_restart: True + service_policy: 'automatic' + service_restart: True configure-vmotion: esxi.vmotion_configured: - - enabled: True + enabled: True configure-host-vsan: esxi.vsan_configured: - - enabled: True - - add_disks_to_vsan: True + enabled: True + add_disks_to_vsan: True configure-host-password: esxi.password_present: - - password: 'new-bad-password' + password: 'new-bad-password' States are called via the ESXi Proxy Minion just as they would on a regular minion. For example: diff --git a/doc/topics/tutorials/gitfs.rst b/doc/topics/tutorials/gitfs.rst index 003181d79686..6ad8fdcbe9df 100644 --- a/doc/topics/tutorials/gitfs.rst +++ b/doc/topics/tutorials/gitfs.rst @@ -202,7 +202,7 @@ including by installing XCode_. GitPython: pip.installed: - - name: 'GitPython < 2.0.9' + name: 'GitPython < 2.0.9' Simple Configuration ==================== @@ -858,14 +858,14 @@ Consider the following example top file and SLS file: manage_mystuff: pkg.installed: - - name: mystuff + name: mystuff file.managed: - - name: /etc/mystuff.conf - - source: salt://mystuff/files/mystuff.conf + name: /etc/mystuff.conf + source: salt://mystuff/files/mystuff.conf service.running: - - name: mystuffd - - enable: True - - watch: + name: mystuffd + enable: True + watch: - file: /etc/mystuff.conf Imagine for a moment that you need to change your ``mystuff.conf``. So, you go diff --git a/doc/topics/tutorials/http.rst b/doc/topics/tutorials/http.rst index 33230654e1cf..885fa1f4e93b 100644 --- a/doc/topics/tutorials/http.rst +++ b/doc/topics/tutorials/http.rst @@ -478,28 +478,28 @@ Therefore, the following states are valid: http://example.com/restapi: http.query: - - match: 'SUCCESS' - - username: 'larry' - - password: '5700g3543v4r' - - data_render: True - - header_file: /tmp/headers.txt - - data_file: /tmp/data.txt - - header_render: True - - cookies: True - - persist_session: True + match: 'SUCCESS' + username: 'larry' + password: '5700g3543v4r' + data_render: True + header_file: /tmp/headers.txt + data_file: /tmp/data.txt + header_render: True + cookies: True + persist_session: True http://example.com/restapi: http.query: - - match_type: pcre - - match: '(?i)succe[ss|ed]' - - username: 'larry' - - password: '5700g3543v4r' - - data_render: True - - header_file: /tmp/headers.txt - - data_file: /tmp/data.txt - - header_render: True - - cookies: True - - persist_session: True + match_type: pcre + match: '(?i)succe[ss|ed]' + username: 'larry' + password: '5700g3543v4r' + data_render: True + header_file: /tmp/headers.txt + data_file: /tmp/data.txt + header_render: True + cookies: True + persist_session: True In addition to, or instead of a match pattern, the status code for a URL can be checked. This is done using the ``status`` argument: @@ -508,7 +508,7 @@ checked. This is done using the ``status`` argument: http://example.com/: http.query: - - status: 200 + status: 200 If both are specified, both will be checked, but if only one is ``True`` and the other is ``False``, then ``False`` will be returned. In this case, the comments diff --git a/doc/topics/tutorials/libcloud.rst b/doc/topics/tutorials/libcloud.rst index 086696fdb871..19097dc84ced 100644 --- a/doc/topics/tutorials/libcloud.rst +++ b/doc/topics/tutorials/libcloud.rst @@ -180,29 +180,29 @@ This could be combined with a multi-cloud load balancer deployment, webserver: libcloud_dns.zone_present: - - name: mywebsite.com - - profile: godaddy + name: mywebsite.com + profile: godaddy ... libcloud_loadbalancer.balancer_present: - - name: web_main - - port: 80 - - protocol: http - - members: + name: web_main + port: 80 + protocol: http + members: - ip: 1.2.4.5 port: 80 - ip: 2.4.5.6 port: 80 - - profile: google_gce + profile: google_gce libcloud_loadbalancer.balancer_present: - - name: web_main - - port: 80 - - protocol: http - - members: + name: web_main + port: 80 + protocol: http + members: - ip: 1.2.4.5 port: 80 - ip: 2.4.5.6 port: 80 - - profile: amazon_elb + profile: amazon_elb Extended parameters can be passed to the specific cloud, for example you can specify the region with the Google Cloud API, because `create_balancer` can accept a `ex_region` argument. Adding this argument to the state will pass the additional command to the driver. @@ -211,11 +211,11 @@ Extended parameters can be passed to the specific cloud, for example you can spe lb_test: libcloud_loadbalancer.balancer_absent: - - name: example - - port: 80 - - protocol: http - - profile: google - - ex_region: us-east1 + name: example + port: 80 + protocol: http + profile: google + ex_region: us-east1 Accessing custom arguments in execution modules =============================================== diff --git a/doc/topics/tutorials/lxc.rst b/doc/topics/tutorials/lxc.rst index 385f7a2ad271..ea209c68c3e5 100644 --- a/doc/topics/tutorials/lxc.rst +++ b/doc/topics/tutorials/lxc.rst @@ -538,8 +538,8 @@ To ensure the existence of a named container, use the :mod:`lxc.present # Using a template web1: lxc.present: - - template: download - - options: + template: download + options: dist: centos release: 6 arch: amd64 @@ -547,18 +547,18 @@ To ensure the existence of a named container, use the :mod:`lxc.present # Cloning web2: lxc.present: - - clone_from: web-base + clone_from: web-base # Using a rootfs image web3: lxc.present: - - image: salt://path/to/cent6.tar.gz + image: salt://path/to/cent6.tar.gz # Using profiles web4: lxc.present: - - profile: centos_web - - network_profile: centos + profile: centos_web + network_profile: centos .. warning:: @@ -610,7 +610,7 @@ of these states: # Restart the container if it was already running web2: lxc.running: - - restart: True + restart: True web3: lxc.stopped @@ -618,7 +618,7 @@ of these states: # Explicitly kill all tasks in container instead of gracefully stopping web4: lxc.stopped: - - kill: True + kill: True web5: lxc.frozen @@ -626,4 +626,4 @@ of these states: # If container is stopped, do not start it (in which case the state will fail) web6: lxc.frozen: - - start: False + start: False diff --git a/doc/topics/tutorials/pillar.rst b/doc/topics/tutorials/pillar.rst index e60675fcd793..40ab3be05ac8 100644 --- a/doc/topics/tutorials/pillar.rst +++ b/doc/topics/tutorials/pillar.rst @@ -165,7 +165,7 @@ state, you can use Jinja: {% for user, uid in pillar.get('users', {}).items() %} {{user}}: user.present: - - uid: {{uid}} + uid: {{uid}} {% endfor %} This approach allows for users to be safely defined in a pillar and then the @@ -220,7 +220,7 @@ inside of the pillar, so sls files can be safely parameterized: apache: pkg.installed: - - name: {{ pillar['pkgs']['apache'] }} + name: {{ pillar['pkgs']['apache'] }} Or, if no pillar is available a default can be set as well: @@ -235,7 +235,7 @@ Or, if no pillar is available a default can be set as well: apache: pkg.installed: - - name: {{ salt['pillar.get']('pkgs:apache', 'httpd') }} + name: {{ salt['pillar.get']('pkgs:apache', 'httpd') }} In the above example, if the pillar value ``pillar['pkgs']['apache']`` is not set in the minion's pillar, then the default of ``httpd`` will be used. @@ -262,11 +262,11 @@ A simple formula: /etc/vimrc: file.managed: - - source: salt://edit/vimrc - - mode: 644 - - user: root - - group: root - - require: + source: salt://edit/vimrc + mode: 644 + user: root + group: root + require: - pkg: vim Can be easily transformed into a powerful, parameterized formula: @@ -277,15 +277,15 @@ Can be easily transformed into a powerful, parameterized formula: vim: pkg.installed: - - name: {{ pillar['pkgs']['vim'] }} + name: {{ pillar['pkgs']['vim'] }} /etc/vimrc: file.managed: - - source: {{ pillar['vimrc'] }} - - mode: 644 - - user: root - - group: root - - require: + source: {{ pillar['vimrc'] }} + mode: 644 + user: root + group: root + require: - pkg: vim Where the vimrc source location can now be changed via pillar: @@ -394,8 +394,8 @@ problem if you do this: mysql-admin-passwd: mysql_user.present: - - name: root - - password: somepasswd + name: root + password: somepasswd mydb: mysql_db.present diff --git a/doc/topics/tutorials/starting_states.rst b/doc/topics/tutorials/starting_states.rst index 35dddb59edbb..5dca17120f02 100644 --- a/doc/topics/tutorials/starting_states.rst +++ b/doc/topics/tutorials/starting_states.rst @@ -70,7 +70,7 @@ A typical SLS file will often look like this in YAML: apache: pkg.installed: [] service.running: - - require: + require: - pkg: apache This SLS data will ensure that the package named apache is installed, and @@ -105,28 +105,28 @@ and a user and group may need to be set up. apache: pkg.installed: [] service.running: - - watch: + watch: - pkg: apache - file: /etc/httpd/conf/httpd.conf - user: apache user.present: - - uid: 87 - - gid: 87 - - home: /var/www/html - - shell: /bin/nologin - - require: + uid: 87 + gid: 87 + home: /var/www/html + shell: /bin/nologin + require: - group: apache group.present: - - gid: 87 - - require: + gid: 87 + require: - pkg: apache /etc/httpd/conf/httpd.conf: file.managed: - - source: salt://apache/httpd.conf - - user: root - - group: root - - mode: 644 + source: salt://apache/httpd.conf + user: root + group: root + mode: 644 This SLS data greatly extends the first example, and includes a config file, a user, a group and new requisite statement: ``watch``. @@ -185,11 +185,11 @@ the toolkit. Consider this SSH example: /etc/ssh/ssh_config: file.managed: - - user: root - - group: root - - mode: 644 - - source: salt://ssh/ssh_config - - require: + user: root + group: root + mode: 644 + source: salt://ssh/ssh_config + require: - pkg: openssh-client ``ssh/server.sls:`` @@ -204,7 +204,7 @@ the toolkit. Consider this SSH example: sshd: service.running: - - require: + require: - pkg: openssh-client - pkg: openssh-server - file: /etc/ssh/banner @@ -212,11 +212,11 @@ the toolkit. Consider this SSH example: /etc/ssh/sshd_config: file.managed: - - user: root - - group: root - - mode: 644 - - source: salt://ssh/sshd_config - - require: + user: root + group: root + mode: 644 + source: salt://ssh/sshd_config + require: - pkg: openssh-server /etc/ssh/banner: @@ -296,7 +296,7 @@ add more watchers to apache to include mod_python. extend: apache: service: - - watch: + watch: - pkg: mod_python mod_python: @@ -371,34 +371,34 @@ for the Grains to be accessed from within the template. A few examples: apache: pkg.installed: {% if grains['os'] == 'RedHat'%} - - name: httpd + name: httpd {% endif %} service.running: {% if grains['os'] == 'RedHat'%} - - name: httpd + name: httpd {% endif %} - - watch: + watch: - pkg: apache - file: /etc/httpd/conf/httpd.conf - user: apache user.present: - - uid: 87 - - gid: 87 - - home: /var/www/html - - shell: /bin/nologin - - require: + uid: 87 + gid: 87 + home: /var/www/html + shell: /bin/nologin + require: - group: apache group.present: - - gid: 87 - - require: + gid: 87 + require: - pkg: apache /etc/httpd/conf/httpd.conf: file.managed: - - source: salt://apache/httpd.conf - - user: root - - group: root - - mode: 644 + source: salt://apache/httpd.conf + user: root + group: root + mode: 644 This example is simple. If the ``os`` grain states that the operating system is Red Hat, then the name of the Apache package and service needs to be httpd. @@ -418,42 +418,42 @@ a MooseFS distributed filesystem chunkserver: {% for mnt in salt['cmd.run']('ls /dev/data/moose*').split() %} /mnt/moose{{ mnt[-1] }}: mount.mounted: - - device: {{ mnt }} - - fstype: xfs - - mkmnt: True + device: {{ mnt }} + fstype: xfs + mkmnt: True file.directory: - - user: mfs - - group: mfs - - require: + user: mfs + group: mfs + require: - user: mfs - group: mfs {% endfor %} /etc/mfshdd.cfg: file.managed: - - source: salt://moosefs/mfshdd.cfg - - user: root - - group: root - - mode: 644 - - template: jinja - - require: + source: salt://moosefs/mfshdd.cfg + user: root + group: root + mode: 644 + template: jinja + require: - pkg: mfs-chunkserver /etc/mfschunkserver.cfg: file.managed: - - source: salt://moosefs/mfschunkserver.cfg - - user: root - - group: root - - mode: 644 - - template: jinja - - require: + source: salt://moosefs/mfschunkserver.cfg + user: root + group: root + mode: 644 + template: jinja + require: - pkg: mfs-chunkserver mfs-chunkserver: pkg.installed: [] mfschunkserver: service.running: - - require: + require: {% for mnt in salt['cmd.run']('ls /dev/data/moose*') %} - mount: /mnt/moose{{ mnt[-1] }} - file: /mnt/moose{{ mnt[-1] }} diff --git a/doc/topics/tutorials/states_pt1.rst b/doc/topics/tutorials/states_pt1.rst index 5e164c39fcd6..4c9da81ecd80 100644 --- a/doc/topics/tutorials/states_pt1.rst +++ b/doc/topics/tutorials/states_pt1.rst @@ -83,7 +83,7 @@ named ``webserver.sls``, containing the following: .. code-block:: yaml apache: # ID declaration - pkg: # state declaration + pkg: # state module declaration - installed # function declaration The first line, called the :ref:`id-declaration`, is an arbitrary identifier. @@ -95,7 +95,7 @@ In this case it defines the name of the package to be installed. OS or distro — for example, on Fedora it is ``httpd`` but on Debian/Ubuntu it is ``apache2``. -The second line, called the :ref:`state-declaration`, defines which of the Salt +The second line, called the :ref:`state-module-declaration`, defines which of the Salt States we are using. In this example, we are using the :mod:`pkg state ` to ensure that a given package is installed. diff --git a/doc/topics/tutorials/states_pt2.rst b/doc/topics/tutorials/states_pt2.rst index 06148b858b9a..31b2477c270f 100644 --- a/doc/topics/tutorials/states_pt2.rst +++ b/doc/topics/tutorials/states_pt2.rst @@ -27,7 +27,7 @@ You can specify multiple :ref:`state-declaration` under an apache: pkg.installed: [] service.running: - - require: + require: - pkg: apache Try stopping Apache before running :py:func:`state.apply @@ -57,11 +57,11 @@ installed and running. Include the following at the bottom of your apache: pkg.installed: [] service.running: - - require: + require: - pkg: apache /var/www/index.html: # ID declaration - file: # state declaration + file: # state module declaration - managed # function - source: salt://webserver/index.html # function arg - require: # requisite declaration @@ -72,7 +72,7 @@ want to install our custom HTML file. (**Note:** the default location that Apache serves may differ from the above on your OS or distro. ``/srv/www`` could also be a likely place to look.) -**Line 8** the :ref:`state-declaration`. This example uses the Salt :mod:`file +**Line 8** the :ref:`state-module-declaration`. This example uses the Salt :mod:`file state `. **Line 9** is the :ref:`function-declaration`. The :func:`managed function @@ -129,14 +129,14 @@ Verify that Apache is now serving your custom HTML. /etc/httpd/extra/httpd-vhosts.conf: file.managed: - - source: salt://webserver/httpd-vhosts.conf + source: salt://webserver/httpd-vhosts.conf apache: pkg.installed: [] service.running: - - watch: + watch: - file: /etc/httpd/extra/httpd-vhosts.conf - - require: + require: - pkg: apache If the pkg and service names differ on your OS or distro of choice you can diff --git a/doc/topics/tutorials/states_pt3.rst b/doc/topics/tutorials/states_pt3.rst index a43bbaf915cc..19e0fececa43 100644 --- a/doc/topics/tutorials/states_pt3.rst +++ b/doc/topics/tutorials/states_pt3.rst @@ -78,9 +78,9 @@ context. The `grains` can be used from within sls modules: apache: pkg.installed: {% if grains['os'] == 'RedHat' %} - - name: httpd + name: httpd {% elif grains['os'] == 'Ubuntu' %} - - name: apache2 + name: apache2 {% endif %} Using Environment Variables in SLS modules @@ -97,8 +97,8 @@ variable in a Salt state. Create a file with contents from an environment variable: file.managed: - - name: /tmp/hello - - contents: {{ salt['environ.get']('MYENVVAR') }} + name: /tmp/hello + contents: {{ salt['environ.get']('MYENVVAR') }} Error checking: @@ -109,8 +109,8 @@ Error checking: Create a file with contents from an environment variable: file.managed: - - name: /tmp/hello - - contents: {{ salt['environ.get']('MYENVVAR') }} + name: /tmp/hello + contents: {{ salt['environ.get']('MYENVVAR') }} {% else %} @@ -138,7 +138,7 @@ The following example illustrates calling the ``group_to_gid`` function in the moe: user.present: - - gid: {{ salt['file.group_to_gid']('some_group_that_exists') }} + gid: {{ salt['file.group_to_gid']('some_group_that_exists') }} One way to think about this might be that the ``gid`` key is being assigned a value equivalent to the following python pseudo-code: @@ -191,7 +191,7 @@ using an :ref:`include-declaration`. For example: django: pkg.installed: - - require: + require: - pkg: python-dateutil Extend declaration @@ -224,7 +224,7 @@ vhosts file is changed: /etc/httpd/extra/httpd-vhosts.conf: file.managed: - - source: salt://apache/httpd-vhosts.conf + source: salt://apache/httpd-vhosts.conf .. include:: /_incl/extend_with_require_watch.rst @@ -252,8 +252,8 @@ follows: mywebsite: file.managed: - - name: /etc/httpd/extra/httpd-vhosts.conf - - source: salt://apache/httpd-vhosts.conf + name: /etc/httpd/extra/httpd-vhosts.conf + source: salt://apache/httpd-vhosts.conf Names declaration ----------------- @@ -267,7 +267,7 @@ can be rewritten without the loop: stooges: user.present: - - names: + names: - moe - larry - curly diff --git a/doc/topics/tutorials/states_pt4.rst b/doc/topics/tutorials/states_pt4.rst index 943f3079f114..eb1fe4a3c4e0 100644 --- a/doc/topics/tutorials/states_pt4.rst +++ b/doc/topics/tutorials/states_pt4.rst @@ -149,12 +149,12 @@ And finally, the SLS to deploy the website: {% if pillar.get('webserver_role', '') %} /var/www/foobarcom: file.recurse: - - source: salt://webserver/src/foobarcom - - env: {{ pillar['webserver_role'] }} - - user: www - - group: www - - dir_mode: 755 - - file_mode: 644 + source: salt://webserver/src/foobarcom + env: {{ pillar['webserver_role'] }} + user: www + group: www + dir_mode: 755 + file_mode: 644 {% endif %} Given the above SLS, the source for the website should initially be placed in diff --git a/doc/topics/tutorials/syslog_ng-state-usage.rst b/doc/topics/tutorials/syslog_ng-state-usage.rst index cc9450aa66c7..3ed0d8403e88 100644 --- a/doc/topics/tutorials/syslog_ng-state-usage.rst +++ b/doc/topics/tutorials/syslog_ng-state-usage.rst @@ -30,7 +30,7 @@ A statement can be declared in the following forms (both are equivalent): source.s_localhost: syslog_ng.config: - - config: + config: - tcp: - ip: "127.0.0.1" - port: 1233 @@ -39,7 +39,7 @@ A statement can be declared in the following forms (both are equivalent): s_localhost: syslog_ng.config: - - config: + config: source: - tcp: - ip: "127.0.0.1" @@ -66,71 +66,71 @@ The following configuration is an example, how a complete syslog-ng configuratio # Set the location of the configuration file set_location: module.run: - - name: syslog_ng.set_config_file - - m_name: "/home/tibi/install/syslog-ng/etc/syslog-ng.conf" + name: syslog_ng.set_config_file + m_name: "/home/tibi/install/syslog-ng/etc/syslog-ng.conf" # The syslog-ng and syslog-ng-ctl binaries are here. You needn't use # this method if these binaries can be found in a directory in your PATH. set_bin_path: module.run: - - name: syslog_ng.set_binary_path - - m_name: "/home/tibi/install/syslog-ng/sbin" + name: syslog_ng.set_binary_path + m_name: "/home/tibi/install/syslog-ng/sbin" # Writes the first lines into the config file, also erases its previous # content write_version: module.run: - - name: syslog_ng.write_version - - m_name: "3.6" + name: syslog_ng.write_version + m_name: "3.6" # There is a shorter form to set the above variables set_variables: module.run: - - name: syslog_ng.set_parameters - - version: "3.6" - - binary_path: "/home/tibi/install/syslog-ng/sbin" - - config_file: "/home/tibi/install/syslog-ng/etc/syslog-ng.conf" + name: syslog_ng.set_parameters + version: "3.6" + binary_path: "/home/tibi/install/syslog-ng/sbin" + config_file: "/home/tibi/install/syslog-ng/etc/syslog-ng.conf" # Some global options options.global_options: syslog_ng.config: - - config: + config: - time_reap: 30 - mark_freq: 10 - keep_hostname: "yes" source.s_localhost: syslog_ng.config: - - config: + config: - tcp: - ip: "127.0.0.1" - port: 1233 destination.d_log_server: syslog_ng.config: - - config: + config: - tcp: - "127.0.0.1" - port: 1234 log.l_log_to_central_server: syslog_ng.config: - - config: + config: - source: s_localhost - destination: d_log_server some_comment: module.run: - - name: syslog_ng.write_config - - config: | + name: syslog_ng.write_config + config: | # Multi line # comment # Another mode to use comments or existing configuration snippets config.other_comment_form: syslog_ng.config: - - config: | + config: | # Multi line # comment @@ -234,7 +234,7 @@ Simple source s_tail: # Salt will call the source function of syslog_ng module syslog_ng.config: - - config: + config: source: - file: - file: ''"/var/log/apache/access.log"'' @@ -249,7 +249,7 @@ OR s_tail: syslog_ng.config: - - config: + config: source: - file: - ''"/var/log/apache/access.log"'' @@ -264,7 +264,7 @@ OR source.s_tail: syslog_ng.config: - - config: + config: - file: - ''"/var/log/apache/access.log"'' - follow_freq : 1 @@ -289,7 +289,7 @@ Complex source s_gsoc2014: syslog_ng.config: - - config: + config: source: - tcp: - ip: 0.0.0.0 @@ -311,7 +311,7 @@ Filter f_json: syslog_ng.config: - - config: + config: filter: - match: - ''"@json:"'' @@ -334,7 +334,7 @@ Template t_demo_filetemplate: syslog_ng.config: - -config: + config: template: - template: - '"$ISODATE $HOST $MSG\n"' @@ -357,7 +357,7 @@ Rewrite r_set_message_to_MESSAGE: syslog_ng.config: - - config: + config: rewrite: - set: - '"${.json.message}"' @@ -378,7 +378,7 @@ Global options global_options: syslog_ng.config: - - config: + config: options: - time_reap: 30 - mark_freq: 10 @@ -428,7 +428,7 @@ Log l_gsoc2014: syslog_ng.config: - - config: + config: log: - source: s_gsoc2014 - junction: diff --git a/doc/topics/tutorials/walkthrough.rst b/doc/topics/tutorials/walkthrough.rst index 10e5019d7043..0626db77fcf0 100644 --- a/doc/topics/tutorials/walkthrough.rst +++ b/doc/topics/tutorials/walkthrough.rst @@ -523,10 +523,10 @@ Now, to beef up the vim SLS formula, a ``vimrc`` can be added: /etc/vimrc: file.managed: - - source: salt://vimrc - - mode: 644 - - user: root - - group: root + source: salt://vimrc + mode: 644 + user: root + group: root Now the desired ``vimrc`` needs to be copied into the Salt file server to ``/srv/salt/vimrc``. In Salt, everything is a file, so no path redirection needs @@ -555,7 +555,7 @@ make an nginx subdirectory and add an init.sls file: nginx: pkg.installed: [] service.running: - - require: + require: - pkg: nginx A few concepts are introduced in this SLS formula. @@ -605,10 +605,10 @@ called ``edit`` and change the ``vim.sls`` file to reflect the change: /etc/vimrc: file.managed: - - source: salt://edit/vimrc - - mode: 644 - - user: root - - group: root + source: salt://edit/vimrc + mode: 644 + user: root + group: root Only the source path to the vimrc file has changed. Now the formula is referenced as ``edit.vim`` because it resides in the edit subdirectory. diff --git a/doc/topics/tutorials/walkthrough_macosx.rst b/doc/topics/tutorials/walkthrough_macosx.rst index 3a966291a9b6..a87823a982c4 100644 --- a/doc/topics/tutorials/walkthrough_macosx.rst +++ b/doc/topics/tutorials/walkthrough_macosx.rst @@ -457,10 +457,10 @@ Now create ``/srv/salt/bin/nginx.sls`` containing the following: nginx: pkg.installed: - - name: nginx + name: nginx service.running: - - enable: True - - reload: True + enable: True + reload: True Check Minion State ------------------ diff --git a/doc/topics/venafi/index.rst b/doc/topics/venafi/index.rst index 105864241a12..f25bd6f9edbc 100644 --- a/doc/topics/venafi/index.rst +++ b/doc/topics/venafi/index.rst @@ -137,15 +137,15 @@ Example state (SLS) file: /etc/ssl/cert/www.example.com.crt: file.managed: - - contents_pillar: venafi:www.example.com:cert - - replace: True + contents_pillar: venafi:www.example.com:cert + replace: True /etc/ssl/cert/www.example.com.key: file.managed: - - contents_pillar: venafi:www.example.com:pkey - - replace: True + contents_pillar: venafi:www.example.com:pkey + replace: True /etc/ssl/cert/www.example.com-chain.pem: file.managed: - - contents_pillar: venafi:www.example.com:chain - - replace: True + contents_pillar: venafi:www.example.com:chain + replace: True diff --git a/salt/client/ssh/wrapper/x509_v2.py b/salt/client/ssh/wrapper/x509_v2.py index 770ec259a558..2078263847d0 100644 --- a/salt/client/ssh/wrapper/x509_v2.py +++ b/salt/client/ssh/wrapper/x509_v2.py @@ -939,7 +939,7 @@ def certificate_managed_wrapper( ret[name + "_crt"] = { "x509.certificate_managed_ssh": [{k: v} for k, v in cert_ret.items()] } - ret[name + "_crt"]["x509.certificate_managed_ssh"].append( + ret[name + "_crt"]["x509.certificate_managed_ssh"].extend( {k: v} for k, v in cert_file_args.items() ) except (CommandExecutionError, SaltInvocationError) as err: diff --git a/salt/state.py b/salt/state.py index b3432215eb5f..2b2e6a4bfcc7 100644 --- a/salt/state.py +++ b/salt/state.py @@ -26,8 +26,16 @@ import site import time import traceback -from collections.abc import Callable, Hashable, Iterable, Mapping, Sequence -from typing import Any, Union +from collections.abc import ( + Callable, + Hashable, + Iterable, + Iterator, + Mapping, + MutableMapping, + Sequence, +) +from typing import Any, TypeVar import networkx as nx @@ -69,12 +77,16 @@ # that have dict values. This could be cleaned up some by making # exclude and extend into dicts instead of lists so the type of all the values are # homogeneous. -HighData = dict[str, Union[Mapping[str, Any], list[Union[Mapping[str, Any], str]]]] +HighData = dict[str, Mapping[str, Any] | list[Mapping[str, Any] | str]] LowChunk = dict[str, Any] +K = TypeVar("K") +V = TypeVar("V") +Pair = tuple[K, V] +HighDataStateArgsDef = Mapping[K, V] | Iterable[Mapping[K, V] | str] # These are keywords passed to state module functions which are to be used -# by salt in this state module and not on the actual state module function +# by salt in this state module and not by the actual state module function STATE_REQUISITE_KEYWORDS = frozenset( [req_type.value for req_type in RequisiteType] + [ @@ -97,8 +109,6 @@ "retry", "order", "parallel", - "prereq", - "prereq_in", "reload_modules", "reload_grains", "reload_pillar", @@ -127,7 +137,6 @@ "__umask__", ] ) - STATE_INTERNAL_KEYWORDS = STATE_REQUISITE_KEYWORDS.union( STATE_REQUISITE_IN_KEYWORDS ).union(STATE_RUNTIME_KEYWORDS) @@ -189,21 +198,19 @@ def get_accumulator_dir(cachedir): return fn_ -def state_args(id_: Hashable, state: Hashable, high: HighData) -> set[Any]: +def state_args(id_: str, state: str, high: HighData) -> set[Any]: """ - Return a set of the arguments passed to the named state + Return a set of the argument names passed to the named state """ args = set() if id_ not in high: return args - if state not in high[id_]: + body = high[id_] + if not isinstance(body, Mapping) or state not in body: return args - for item in high[id_][state]: - if not isinstance(item, dict): - continue - if len(item) != 1: - continue - args.add(next(iter(item))) + for key, _ in _state_args_kv_iter(body[state], id_): + if key != "fun": + args.add(key) return args @@ -251,7 +258,7 @@ def find_sls_ids(sls: Any, high: HighData) -> list[tuple[str, str]]: ret = [] for nid, item in high.items(): try: - sls_tgt = item["__sls__"] + sls_tgt = item["__sls__"] # type: ignore (except case handles invalid type) except TypeError: if nid != "__exclude__": log.error( @@ -281,11 +288,11 @@ def format_log(ret: Any) -> None: if ret["comment"]: msg = ret["comment"] else: - msg = "No changes made for {0[name]}".format(ret) + msg = f"No changes made for {ret['name']}" elif isinstance(chg, dict): if "diff" in chg: if isinstance(chg["diff"], str): - msg = "File changed:\n{}".format(chg["diff"]) + msg = f"File changed:\n{chg['diff']}" if all([isinstance(x, dict) for x in chg.values()]): if all([("old" in x and "new" in x) for x in chg.values()]): msg = "Made the following changes:\n" @@ -298,9 +305,7 @@ def format_log(ret: Any) -> None: new = "absent" # This must be able to handle unicode as some package names contain # non-ascii characters like "Français" or "Español". See Issue #33605. - msg += "'{}' changed from '{}' to '{}'\n".format( - pkg, old, new - ) + msg += f"'{pkg}' changed from '{old}' to '{new}'\n" if not msg: msg = str(ret["changes"]) if ret["result"] is True or ret["result"] is None: @@ -388,6 +393,42 @@ def _apply_exclude(high: HighData) -> HighData: return high +def _state_args_kv_iter( + obj: HighDataStateArgsDef, + id_: Hashable, +) -> Iterator[Pair]: + """ + Yield (key, value) pairs from either: + - Mapping[K, V] -> yields its .items() + - Iterable of elements where each element is either: + * Mapping[K, V] + * str -> yields Mapping["fun", V] + """ + if isinstance(obj, Mapping): + yield from obj.items() + return + + try: + for i, item in enumerate(obj): + if isinstance(item, Mapping): + yield from item.items() + elif isinstance(item, str): + yield ("fun", item) + else: + log.warning( + "Ignoring argument item %s with unsupported element type %r at index %d of %s of state %s", + item, + type(item), + i, + obj, + id_, + ) + except TypeError as ex: + raise TypeError( + f"expected a mapping or a list of str/mapping for state arguments of state {id_} instead of {type(obj)!r} {obj}" + ) from ex + + def _verify_high(high: dict) -> list[str]: """ Verify that the high data is viable and follows the data structure @@ -407,27 +448,34 @@ def _verify_high(high: dict) -> list[str]: err = f"The type {id_} in {body} is not formatted as a dictionary" errors.append(err) continue - for state in body: + for state, args in body.items(): if state.startswith("__"): continue - if body[state] is None: + if args is None: errors.append( f"ID '{id_}' in SLS '{body['__sls__']}' contains a short declaration " f"({state}) with a trailing colon. When not passing any " "arguments to a state, the colon must be omitted." ) continue - if not isinstance(body[state], list): + if not isinstance(args, list) and not isinstance(args, dict): errors.append( - f"State '{id_}' in SLS '{body['__sls__']}' is not formed as a list" + f"State '{id_}' in SLS '{body['__sls__']}' is not formed as a list or dict" ) + continue + fun_count = 0 + if "." in state: + # This should not happen usually since `_handle_state_decls` or + # `pad_funcs` is run on rendered templates + fun_count += 1 + if isinstance(args, dict): + for key, value in args.items(): + if key == "fun": + fun_count += 1 + else: + _verify_high_state_key_value_arg(id_, key, value, body, errors) else: - fun_count = 0 - if "." in state: - # This should not happen usually since `_handle_state_decls` or - # `pad_funcs` is run on rendered templates - fun_count += 1 - for arg in body[state]: + for arg in args: if isinstance(arg, str): fun_count += 1 if " " in arg.strip(): @@ -440,75 +488,76 @@ def _verify_high(high: dict) -> list[str]: ) elif isinstance(arg, dict): - # The arg is a dict, if the arg is require or - # watch, it must be a list. - argfirst = next(iter(arg)) - if argfirst == "names": - if not isinstance(arg[argfirst], list): - errors.append( - "The 'names' argument in state " - f"'{id_}' in SLS '{body['__sls__']}' needs to be " - "formed as a list" - ) - if argfirst in STATE_REQUISITE_KEYWORDS: - if not isinstance(arg[argfirst], list): - errors.append( - f"The {argfirst} statement in state '{id_}' in " - f"SLS '{body['__sls__']}' needs to be formed as a " - "list" - ) - # It is a list, verify that the members of the - # list are all single key dicts. - else: - for req in arg[argfirst]: - if isinstance(req, str): - req = {"id": req} - if not isinstance(req, dict) or len(req) != 1: - errors.append( - f"Requisite declaration {req} in " - f"state {id_} in SLS {body['__sls__']} " - "is not formed as a single key dictionary" - ) - continue - # req_key: the name or id of the required state; the requisite will match both - # req_val: the type of requirement i.e. id, sls, name of state module like file - req_key, req_val = next(iter(req.items())) - if "." in req_key: - errors.append( - f"Invalid requisite type '{req_key}' " - f"in state '{id_}', in SLS " - f"'{ body['__sls__']}'. Requisite types must " - "not contain dots, did you " - f"mean '{req_key[: req_key.find('.')]}'?" - ) - if not ishashable(req_val): - errors.append( - f'Illegal requisite "{req_val}" ' - f'in SLS "{body["__sls__"]}", ' - "please check your syntax.\n" - ) - continue - # Make sure that there is only one key in the - # dict - if len(list(arg)) != 1: - errors.append( - "Multiple dictionaries defined in " - f"argument of state '{id_}' in SLS '{body['__sls__']}'" - ) - if not fun_count: + first_key, first_value = next(iter(arg.items())) + _verify_high_state_key_value_arg( + id_, first_key, first_value, body, errors + ) + if not fun_count: + errors.append( + f"No function declared in state '{id_}' in SLS " + f"'{body['__sls__']}'" + ) + elif fun_count > 1: + funs = [state.split(".", maxsplit=1)[1]] if "." in state else [] + funs.extend(arg for arg in body[state] if isinstance(arg, str)) + errors.append( + f"Too many functions declared in state '{id_}' in " + f"SLS '{body['__sls__']}'. Please choose one of " + "the following: " + ", ".join(funs) + ) + return errors + + +def _verify_high_state_key_value_arg( + id_: str, arg_name: str, value, body: dict, errors: list[str] +): + if arg_name == "names": + if not isinstance(value, list): + errors.append( + "The 'names' argument in state " + f"'{id_}' in SLS '{body['__sls__']}' needs to be " + "formed as a list" + ) + elif arg_name in STATE_REQUISITE_KEYWORDS: + # if the arg is a requisite, + # it must be a list. + if not isinstance(value, list): + errors.append( + f"The {arg_name} statement in state '{id_}' in " + f"SLS '{body['__sls__']}' needs to be formed as a " + "list" + ) + # It is a list, verify that the members of the + # list are all single key dicts. + else: + for req in value: + if isinstance(req, str): + req = {"id": req} + if not isinstance(req, dict) or len(req) != 1: errors.append( - f"No function declared in state '{id_}' in SLS " - f"'{body['__sls__']}'" + f"Requisite declaration {req} in " + f"state {id_} in SLS {body['__sls__']} " + "is not formed as a single key dictionary" ) - elif fun_count > 1: - funs = [state.split(".", maxsplit=1)[1]] if "." in state else [] - funs.extend(arg for arg in body[state] if isinstance(arg, str)) + continue + # req_key: the name or id of the required state; the requisite will match both + # req_val: the type of requirement i.e. id, sls, name of state module like file + req_key, req_val = next(iter(req.items())) + if "." in req_key: errors.append( - f"Too many functions declared in state '{id_}' in " - f"SLS '{body['__sls__']}'. Please choose one of " - "the following: " + ", ".join(funs) + f"Invalid requisite type '{req_key}' " + f"in state '{id_}', in SLS " + f"'{ body['__sls__']}'. Requisite types must " + "not contain dots, did you " + f"mean '{req_key[: req_key.find('.')]}'?" ) - return errors + if not ishashable(req_val): + errors.append( + f'Illegal requisite "{req_val}" ' + f'in SLS "{body["__sls__"]}", ' + "please check your syntax.\n" + ) + continue class StateError(Exception): @@ -547,33 +596,27 @@ def pad_funcs(self, high): """ Turns dot delimited function refs into function strings """ - for name in high: - if not isinstance(high[name], dict): - if isinstance(high[name], str): + for id_, body in high.items(): + if not isinstance(body, dict): + if isinstance(body, str): # Is this is a short state? It needs to be padded! - if "." in high[name]: - comps = high[name].split(".") - if len(comps) >= 2: - # Merge the comps - comps[1] = ".".join(comps[1 : len(comps)]) - high[name] = { + if "." in body: + state, function_name = body.split(".", maxsplit=1) + high[id_] = { # '__sls__': template, # '__env__': None, - comps[0]: [comps[1]] + state: [function_name] } continue continue - skeys = set() - for key in sorted(high[name]): + state_keys = set() + for key, value in sorted(body.items()): if key.startswith("_"): continue - if not isinstance(high[name][key], list): + if not isinstance(value, (list, dict)): continue if "." in key: - comps = key.split(".") - if len(comps) >= 2: - # Merge the comps - comps[1] = ".".join(comps[1 : len(comps)]) + state, function_name = key.split(".", maxsplit=1) # Salt doesn't support state files such as: # # /etc/redis/redis.conf: @@ -583,13 +626,16 @@ def pad_funcs(self, high): # - mode: 644 # file.comment: # - regex: ^requirepass - if comps[0] in skeys: + if state in state_keys: continue - high[name][comps[0]] = high[name].pop(key) - high[name][comps[0]].append(comps[1]) - skeys.add(comps[0]) + high[id_][state] = high[id_].pop(key) + if isinstance(value, dict): + high[id_][state][function_name] = None + else: + high[id_][state].append(function_name) + state_keys.add(state) continue - skeys.add(key) + state_keys.add(key) return high def verify_high(self, high): @@ -892,12 +938,10 @@ def _mod_init(self, low): """ # ensure that the module is loaded try: - self.states[ - "{}.{}".format(low["state"], low["fun"]) - ] # pylint: disable=W0106 + self.states[f"{low['state']}.{low['fun']}"] except KeyError: return - minit = "{}.mod_init".format(low["state"]) + minit = f"{low['state']}.mod_init" if low["state"] not in self.mod_init: if minit in self.states._dict: mret = self.states[minit](low) @@ -1192,7 +1236,7 @@ def _run_check_creates(self, low: LowChunk) -> dict[str, Any]: ret[key] = low.get(key) if isinstance(low["creates"], str) and os.path.exists(low["creates"]): - ret["comment"] = "{} exists".format(low["creates"]) + ret["comment"] = f"{low['creates']} exists" ret["result"] = True ret["skip_watch"] = True elif isinstance(low["creates"], list) and all( @@ -1260,7 +1304,7 @@ def load_modules(self, data=None, proxy=None): ) if funcs: for func in funcs: - f_key = "{}{}".format(mod, func[func.rindex(".") :]) + f_key = f"{mod}{func[func.rindex('.') :]}" self.functions[f_key] = funcs[func] self.serializers = salt.loader.serializers(self.opts) self._load_states() @@ -1368,7 +1412,7 @@ def verify_data(self, data: dict[str, Any]) -> list[str]: if full not in self.states: if "__sls__" in data: errors.append( - "State '{}' was not found in SLS '{}'".format(full, data["__sls__"]) + f"State '{full}' was not found in SLS '{data['__sls__']}'" ) reason = self.states.missing_fun_string(full) if reason: @@ -1387,9 +1431,7 @@ def verify_data(self, data: dict[str, Any]) -> list[str]: for ind in range(arglen - deflen): if aspec.args[ind] not in data: errors.append( - "Missing parameter {} for state {}".format( - aspec.args[ind], full - ) + f"Missing parameter {aspec.args[ind]} for state {full}" ) return errors @@ -1475,7 +1517,7 @@ def compile_high_data( for id_, body in high.items(): if id_.startswith("__"): continue - for state, run in body.items(): + for state, args in body.items(): # This should be a single value instead of a set # because multiple functions of the same state # type are not allowed in the same state @@ -1493,24 +1535,21 @@ def compile_high_data( if "__sls_included_from__" in body: chunk["__sls_included_from__"] = body["__sls_included_from__"] chunk["__id__"] = id_ - for arg in run: - if isinstance(arg, str): - funcs.add(arg) + for key, val in _state_args_kv_iter(args, id_): + if key == "fun": + funcs.add(val) + elif key == "names": + for _name in val: + if _name not in names: + names.append(_name) + elif key == "state": + # Don't pass down a state override continue - if isinstance(arg, dict): - for key, val in arg.items(): - if key == "names": - for _name in val: - if _name not in names: - names.append(_name) - elif key == "state": - # Don't pass down a state override - continue - elif key == "name" and not isinstance(val, str): - # Invalid name, fall back to ID - chunk[key] = id_ - else: - chunk[key] = val + elif key == "name" and not isinstance(val, str): + # Invalid name, fall back to ID + chunk[key] = id_ + else: + chunk[key] = val if names: name_order = 1 for entry in names: @@ -1593,7 +1632,7 @@ def reconcile_extend(self, high: HighData, strict=False): errors = [] if "__extend__" not in high: return high, errors - ext = high.pop("__extend__") + ext: list[Mapping[str, Any]] = high.pop("__extend__") for ext_chunk in ext: for name, body in ext_chunk.items(): state_type = next(x for x in body if not x.startswith("__")) @@ -1746,15 +1785,9 @@ def requisite_in(self, high: HighData): extend[name] = HashableOrderedDict() if "." in _state: errors.append( - "Invalid requisite in {}: {} for " - "{} in SLS '{}'. Requisites must " - "not contain dots, did you mean '{}'?".format( - rkey, - _state, - name, - body["__sls__"], - _state[: _state.find(".")], - ) + f"Invalid requisite in {rkey}: {_state} for " + f"{name} in SLS '{body['__sls__']}'. Requisites must " + f"not contain dots, did you mean '{_state[: _state.find('.')]}'?" ) _state = _state.split(".")[0] if _state not in extend[name]: @@ -1983,10 +2016,8 @@ def _call_parallel_target( _comment = "" else: _comment = ( - 'Attempt {}: Returned a result of "{}", ' - 'with the following comment: "{}"'.format( - retries, ret["result"], ret["comment"] - ) + f'Attempt {retries}: Returned a result of "{ret["result"]}", ' + f'with the following comment: "{ret["comment"]}"' ) ret["comment"] = "\n".join([orig_ret["comment"], _comment]) @@ -2141,7 +2172,7 @@ def call( if "provider" in low: self.load_modules(low) - state_func_name = "{0[state]}.{0[fun]}".format(low) + state_func_name = f"{low['state']}.{low['fun']}" cdata = salt.utils.args.format_call( self.states[state_func_name], low, @@ -2182,7 +2213,7 @@ def call( req_list = ("unless", "onlyif", "creates") if ( any(req in low for req in req_list) - and "{0[state]}.mod_run_check".format(low) not in self.states + and f"{low['state']}.mod_run_check" not in self.states ): ret.update(self._run_check(low)) @@ -2233,8 +2264,8 @@ def call( ) self.states.inject_globals = {} if "check_cmd" in low: - state_check_cmd = "{0[state]}.mod_run_check_cmd".format(low) - state_func = "{0[state]}.{0[fun]}".format(low) + state_check_cmd = f"{low['state']}.mod_run_check_cmd" + state_func = f"{low['state']}.{low['fun']}" state_func_sig = inspect.signature(self.states[state_func]) if state_check_cmd not in self.states: ret.update(self._run_check_cmd(low)) @@ -2325,10 +2356,8 @@ def call( ret = retry_ret ret["comment"] = "\n".join( [ - 'Attempt {}: Returned a result of "{}", ' - 'with the following comment: "{}"'.format( - retries, orig_ret["result"], orig_ret["comment"] - ), + f'Attempt {retries}: Returned a result of "{orig_ret["result"]}", ' + f'with the following comment: "{orig_ret["comment"]}"', "" if not ret["comment"] else ret["comment"], ] ) @@ -2934,9 +2963,7 @@ def call_chunk( # If the result was False (not None) it was a failure if req_ret["result"] is False: # use SLS.ID for the key-- so its easier to find - key = "{sls}.{_id}".format( - sls=req_low["__sls__"], _id=req_low["__id__"] - ) + key = f"{req_low['__sls__']}.{req_low['__id__']}" failed_requisites.add(key) _cmt = "One or more requisite failed: {}".format( @@ -3140,9 +3167,7 @@ def call_listen(self, chunks: Iterable[LowChunk], running: dict) -> dict: rerror = { _l_tag(lkey, lval): { "comment": ( - "Referenced state {}: {} does not exist".format( - lkey, lval - ) + f"Referenced state {lkey}: {lval} does not exist" ), "name": f"listen_{lkey}:{lval}", "result": False, @@ -3167,13 +3192,9 @@ def call_listen(self, chunks: Iterable[LowChunk], running: dict) -> dict: rerror = { _l_tag(key[0], key[1]): { "comment": ( - "Referenced state {}: {} does not exist".format( - key[0], key[1] - ) - ), - "name": "listen_{}:{}".format( - key[0], key[1] + f"Referenced state {key[0]}: {key[1]} does not exist" ), + "name": f"listen_{key[0]}:{key[1]}", "result": False, "changes": {}, } @@ -3190,7 +3211,7 @@ def call_listen(self, chunks: Iterable[LowChunk], running: dict) -> dict: low = chunk.copy() low["sfun"] = chunk["fun"] low["fun"] = "mod_watch" - low["__id__"] = "listener_{}".format(low["__id__"]) + low["__id__"] = f"listener_{low['__id__']}" for req in STATE_REQUISITE_KEYWORDS: if req in low: low.pop(req) @@ -3231,7 +3252,6 @@ def call_high( return errors # If there are extensions in the highstate, process them and update # the low data chunks - ret = self.call_chunks(chunks, disabled_states=self.disabled_states) ret = self.call_listen(chunks, ret) ret = self.call_beacons(chunks, ret) @@ -3271,8 +3291,8 @@ def render_template(self, high, template): for item in invalid_items: if item in high: errors.append( - "The '{}' declaration found on '{}' is invalid when " - "rendering single templates".format(item, template) + f"The '{item}' declaration found on '{template}' is invalid when " + "rendering single templates" ) return high, errors @@ -3290,9 +3310,7 @@ def render_template(self, high, template): continue errors.append( - "ID {} in template {} is not a dictionary".format( - name, template - ) + f"ID {name} in template {template} is not a dictionary" ) continue skeys = set() @@ -3301,10 +3319,10 @@ def render_template(self, high, template): continue if high[name][key] is None: errors.append( - "ID '{}' in template {} contains a short " - "declaration ({}) with a trailing colon. When not " + f"ID '{name}' in template {template} contains a short " + f"declaration ({key}) with a trailing colon. When not " "passing any arguments to a state, the colon must be " - "omitted.".format(name, template, key) + "omitted." ) continue if not isinstance(high[name][key], list): @@ -3322,8 +3340,8 @@ def render_template(self, high, template): # - regex: ^requirepass if comps[0] in skeys: errors.append( - "ID '{}' in template '{}' contains multiple " - "state declarations of the same type".format(name, template) + f"ID '{name}' in template '{template}' contains multiple " + "state declarations of the same type" ) continue high[name][comps[0]] = high[name].pop(key) @@ -3851,16 +3869,14 @@ def verify_tops(self, tops): continue if not isinstance(saltenv, str): errors.append( - "Environment {} in top file is not formed as a string".format( - saltenv - ) + f"Environment {saltenv} in top file is not formed as a string" ) if saltenv == "": errors.append("Empty saltenv statement in top file") if not isinstance(matches, dict): errors.append( - "The top file matches for saltenv {} are not " - "formatted as a dict".format(saltenv) + f"The top file matches for saltenv {saltenv} are not " + "formatted as a dict" ) for slsmods in matches.values(): if not isinstance(slsmods, list): @@ -3875,15 +3891,13 @@ def verify_tops(self, tops): if not val: errors.append( "Improperly formatted top file matcher " - "in saltenv {}: {} file".format(slsmod, val) + f"in saltenv {slsmod}: {val} file" ) elif isinstance(slsmod, str): # This is a sls module if not slsmod: errors.append( - "Environment {} contains an empty sls index".format( - saltenv - ) + f"Environment {saltenv} contains an empty sls index" ) return errors @@ -4023,9 +4037,9 @@ def render_state(self, sls, saltenv, mods, matches, local=False, context=None): state = None if not fn_: errors.append( - "Specified SLS {} in saltenv {} is not " + f"Specified SLS {sls} in saltenv {saltenv} is not " "available on the salt master or through a configured " - "fileserver".format(sls, saltenv) + "fileserver" ) else: try: @@ -4065,8 +4079,8 @@ def render_state(self, sls, saltenv, mods, matches, local=False, context=None): if "include" in state: if not isinstance(state["include"], list): err = ( - "Include Declaration in SLS {} is not formed " - "as a list".format(sls) + f"Include Declaration in SLS {sls} is not formed " + "as a list" ) errors.append(err) else: @@ -4090,10 +4104,8 @@ def render_state(self, sls, saltenv, mods, matches, local=False, context=None): if env_key not in self.avail and "__env__" not in self.avail: msg = ( - "Nonexistent saltenv '{}' found in include " - "of '{}' within SLS '{}:{}'".format( - env_key, inc_sls, saltenv, sls - ) + f"Nonexistent saltenv '{env_key}' found in include " + f"of '{inc_sls}' within SLS '{saltenv}:{sls}'" ) log.error(msg) errors.append(msg) @@ -4105,8 +4117,8 @@ def render_state(self, sls, saltenv, mods, matches, local=False, context=None): levels, include = match.groups() else: msg = ( - "Badly formatted include {} found in include " - "in SLS '{}:{}'".format(inc_sls, saltenv, sls) + f"Badly formatted include {inc_sls} found in include " + f"in SLS '{saltenv}:{sls}'" ) log.error(msg) errors.append(msg) @@ -4117,11 +4129,9 @@ def render_state(self, sls, saltenv, mods, matches, local=False, context=None): p_comps.append("init") if level_count > len(p_comps): msg = ( - "Attempted relative include of '{}' " - "within SLS '{}:{}' " - "goes beyond top level package ".format( - inc_sls, saltenv, sls - ) + f"Attempted relative include of '{inc_sls}' " + f"within SLS '{saltenv}:{sls}' " + "goes beyond top level package " ) log.error(msg) errors.append(msg) @@ -4203,10 +4213,8 @@ def render_state(self, sls, saltenv, mods, matches, local=False, context=None): ) elif len(resolved_envs) > 1: msg = ( - "Ambiguous include: Specified SLS {}: {} is available" - " on the salt master in multiple available saltenvs: {}".format( - env_key, inc_sls, ", ".join(resolved_envs) - ) + f"Ambiguous include: Specified SLS {env_key}: {inc_sls} is available" + f" on the salt master in multiple available saltenvs: {', '.join(resolved_envs)}" ) log.critical(msg) errors.append(msg) @@ -4223,33 +4231,31 @@ def _handle_iorder(self, state): Take a state and apply the iorder system """ if self.opts["state_auto_order"]: - for name in state: - for s_dec in state[name]: - if not isinstance(s_dec, str): + for body in state.values(): + if not isinstance(body, dict): + # Include's or excludes as lists? + continue + for state_mod_decl, args in body.items(): + if not isinstance(state_mod_decl, str): # PyDSL OrderedDict? continue - - if not isinstance(state[name], dict): - # Include's or excludes as lists? + if state_mod_decl.startswith("_"): continue - if not isinstance(state[name][s_dec], list): - # Bad syntax, let the verify seq pick it up later on + if isinstance(args, MutableMapping): + if "order" not in args: + args["order"] = self.iorder + self.iorder += 1 continue - - found = False - if s_dec.startswith("_"): + if not isinstance(args, list): + # quite certainly a syntax error, managed elsewhere continue - - for arg in state[name][s_dec]: - if isinstance(arg, dict): - if len(arg) > 0: - if next(iter(arg.keys())) == "order": - found = True + found = False + for arg in args: + if isinstance(arg, dict) and "order" in arg: + found = True + break if not found: - if not isinstance(state[name][s_dec], list): - # quite certainly a syntax error, managed elsewhere - continue - state[name][s_dec].append({"order": self.iorder}) + args.append({"order": self.iorder}) self.iorder += 1 return state @@ -4257,33 +4263,33 @@ def _handle_state_decls(self, state, sls, saltenv, errors): """ Add sls and saltenv components to the state """ - for name in state: - if not isinstance(state[name], dict): - if name == "__extend__": + for id_, body in state.items(): + if not isinstance(body, dict): + if id_ == "__extend__": continue - if name == "__exclude__": + if id_ == "__exclude__": continue - if isinstance(state[name], str): + if isinstance(body, str): # Is this is a short state, it needs to be padded - if "." in state[name]: - comps = state[name].split(".") - state[name] = { + if "." in body: + state_name, function_name = body.split(".", maxsplit=1) + state[id_] = { "__sls__": sls, "__env__": saltenv, - comps[0]: [comps[1]], + state_name: [function_name], } continue - errors.append(f"ID {name} in SLS {sls} is not a dictionary") + errors.append(f"ID {id_} in SLS {sls} is not a dictionary") continue - skeys = set() - for key in list(state[name]): + state_keys = set() + for key, value in list(body.items()): if key.startswith("_"): continue - if not isinstance(state[name][key], list): + if not isinstance(value, (list, dict)): continue if "." in key: - comps = key.split(".") + state_name, function_name = key.split(".", maxsplit=1) # Salt doesn't support state files such as: # # /etc/redis/redis.conf: @@ -4294,21 +4300,24 @@ def _handle_state_decls(self, state, sls, saltenv, errors): # - mode: 644 # file.comment: # - regex: ^requirepass - if comps[0] in skeys: + if state_name in state_keys: errors.append( - "ID '{}' in SLS '{}' contains multiple state " - "declarations of the same type".format(name, sls) + f"ID '{id_}' in SLS '{sls}' contains multiple state " + "declarations of the same type" ) continue - state[name][comps[0]] = state[name].pop(key) - state[name][comps[0]].append(comps[1]) - skeys.add(comps[0]) + body[state_name] = body.pop(key) + if isinstance(value, dict): + body[state_name]["fun"] = function_name + else: + body[state_name].append(function_name) + state_keys.add(state_name) continue - skeys.add(key) - if "__sls__" not in state[name]: - state[name]["__sls__"] = sls - if "__env__" not in state[name]: - state[name]["__env__"] = saltenv + state_keys.add(key) + if "__sls__" not in body: + body["__sls__"] = sls + if "__env__" not in body: + body["__env__"] = saltenv def _handle_extend(self, state, sls, saltenv, errors): """ @@ -4323,9 +4332,7 @@ def _handle_extend(self, state, sls, saltenv, errors): for name in ext: if not isinstance(ext[name], dict): errors.append( - "Extension name '{}' in SLS '{}' is not a dictionary".format( - name, sls - ) + f"Extension name '{name}' in SLS '{sls}' is not a dictionary" ) continue if "__sls__" not in ext[name]: @@ -4351,9 +4358,7 @@ def _handle_exclude(self, state, sls, saltenv, errors): if "exclude" in state: exc = state.pop("exclude") if not isinstance(exc, list): - err = "Exclude Declaration in SLS {} is not formed as a list".format( - sls - ) + err = f"Exclude Declaration in SLS {sls} is not formed as a list" errors.append(err) state.setdefault("__exclude__", []).extend(exc) @@ -4375,7 +4380,7 @@ def render_highstate(self, matches, context=None): else: all_errors.append( "No matching salt environment for environment " - "'{}' found".format(saltenv) + f"'{saltenv}' found" ) # if we did not found any sls in the fileserver listing, this # may be because the sls was generated or added later, we can diff --git a/tests/pytests/functional/modules/state/test_state.py b/tests/pytests/functional/modules/state/test_state.py index 85a7fc4cc3e4..27b356276636 100644 --- a/tests/pytests/functional/modules/state/test_state.py +++ b/tests/pytests/functional/modules/state/test_state.py @@ -7,18 +7,13 @@ import pytest -import salt.loader import salt.modules.cmdmod as cmd import salt.modules.config as config import salt.modules.grains as grains import salt.modules.saltutil as saltutil import salt.modules.state as state_mod -import salt.utils.atomicfile -import salt.utils.files -import salt.utils.path import salt.utils.platform import salt.utils.state as state_util -import salt.utils.stringutils log = logging.getLogger(__name__) @@ -646,6 +641,76 @@ def test_pydsl(state, state_tree, tmp_path): assert testfile.exists() +def test_sls_with_state_args_dict(state, state_tree): + """ + Call sls file with state argument as a dict. + """ + sls_contents = """ + A: + test.succeed_without_changes: + name: echo foo + """ + with pytest.helpers.temp_file("testing.sls", sls_contents, state_tree): + ret = state.sls("testing") + for staterun in ret: + assert staterun.result is True + + +def test_sls_with_state_args_list(state, state_tree): + """ + Call sls file with state argument as a list. + """ + sls_contents = """ + A: + test.succeed_without_changes: + - name: echo foo + """ + with pytest.helpers.temp_file("testing.sls", sls_contents, state_tree): + ret = state.sls("testing") + for staterun in ret: + assert staterun.result is True + + +def test_sls_state_args_formats_equality(state, state_tree): + """ + Check the low data from sls files with state arguments of different + formats and verify they result the same. + """ + sls_contents_list = [ + """ + testname: + test: + - nop + - name: testing + """, + """ + testname: + test.nop: + - name: testing + """, + """ + testname: + test.nop: + name: testing + """, + ] + expected_result = [ + { + "__env__": "base", + "__id__": "testname", + "__sls__": "testing", + "fun": "nop", + "name": "testing", + "order": 10000, + "state": "test", + } + ] + for sls_contents in sls_contents_list: + with pytest.helpers.temp_file("testing.sls", sls_contents, state_tree): + ret = state.show_low_sls("testing") + assert ret == expected_result + + def test_issues_7905_and_8174_sls_syntax_error(state, state_tree): """ Call sls file with yaml syntax error. @@ -680,12 +745,20 @@ def test_issues_7905_and_8174_sls_syntax_error(state, state_tree): "badlist1.sls", badlist_1_sls_contents, state_tree ), pytest.helpers.temp_file("badlist2.sls", badlist_2_sls_contents, state_tree): ret = state.sls("badlist1") - assert ret.failed - assert ret.errors == ["State 'A' in SLS 'badlist1' is not formed as a list"] + staterun = ret["cmd_|-A_|-A_|-run"] + assert staterun.result is False + assert ( + "A: command not found" in staterun.changes["stderr"] + or "'A' is not recognized as an internal or external command" + in staterun.changes["stderr"] + or "A: not found" in staterun.changes["stderr"] + ) ret = state.sls("badlist2") assert ret.failed - assert ret.errors == ["State 'C' in SLS 'badlist2' is not formed as a list"] + assert ret.errors == [ + "State 'C' in SLS 'badlist2' is not formed as a list or dict" + ] def test_retry_option(state, state_tree): @@ -737,7 +810,7 @@ def test_retry_option_is_true(state, state_tree): for state_return in ret: assert state_return.result is False assert expected_comment in state_return.comment - assert state_return.full_return["duration"] >= 3 + assert state_return.full_return["duration"] >= 30 @pytest.mark.skip_initial_gh_actions_failure(skip=_check_skip) @@ -769,7 +842,7 @@ def test_retry_option_success(state, state_tree, tmp_path): assert state_return.result is True assert state_return.full_return["duration"] < duration # It should not take 2 attempts - assert "Attempt 2" not in state_return.comment + assert "Attempt 1" not in state_return.comment @pytest.mark.skip_on_windows( @@ -820,41 +893,44 @@ def test_retry_option_eventual_success(state, state_tree, tmp_path): testfile1 = tmp_path / "testfile-1" testfile2 = tmp_path / "testfile-2" + interval = 2 + def create_testfile(testfile1, testfile2): while True: if testfile1.exists(): break - time.sleep(2) + time.sleep(interval) testfile2.touch() thread = threading.Thread(target=create_testfile, args=(testfile1, testfile2)) - sls_contents = """ + sls_contents = f""" file_test_a: file.managed: - - name: {} + - name: {testfile1} - content: 'a' file_test: file.exists: - - name: {} + - name: {testfile2} - retry: until: True attempts: 5 - interval: 2 + interval: {interval} splay: 0 - require: - file_test_a - """.format( - testfile1, testfile2 - ) + """ with pytest.helpers.temp_file("retry.sls", sls_contents, state_tree): thread.start() ret = state.sls("retry") - for state_return in ret: + for num, state_return in enumerate(ret): assert state_return.result is True - assert state_return.full_return["duration"] > 4 - # It should not take 5 attempts - assert "Attempt 5" not in state_return.comment + assert state_return.full_return["duration"] > interval + if num == 1: + # It should retry at least 1 time + assert "Attempt 1" in state_return.comment + # It should not take 5 attempts + assert "Attempt 5" not in state_return.comment @pytest.mark.skip_on_windows( @@ -867,45 +943,48 @@ def test_retry_option_eventual_success_parallel(state, state_tree, tmp_path): testfile1 = tmp_path / "testfile-1" testfile2 = tmp_path / "testfile-2" + interval = 2 + def create_testfile(testfile1, testfile2): while True: if testfile1.exists(): break - time.sleep(2) + time.sleep(interval) testfile2.touch() thread = threading.Thread(target=create_testfile, args=(testfile1, testfile2)) - sls_contents = """ + sls_contents = f""" file_test_a: file.managed: - - name: {} + - name: {testfile1} - content: 'a' file_test: file.exists: - - name: {} + - name: {testfile2} - retry: until: True attempts: 5 - interval: 2 + interval: {interval} splay: 0 - parallel: True - require: - file_test_a - """.format( - testfile1, testfile2 - ) + """ with pytest.helpers.temp_file("retry.sls", sls_contents, state_tree): thread.start() ret = state.sls( "retry", __pub_jid="1" ) # Because these run in parallel we need a fake JID - for state_return in ret: + for num, state_return in enumerate(ret): log.debug("=== state_return %s ===", state_return) assert state_return.result is True - assert state_return.full_return["duration"] > 4 - # It should not take 5 attempts - assert "Attempt 5" not in state_return.comment + assert state_return.full_return["duration"] > interval + if num == 1: + # It should retry at least 1 time + assert "Attempt 1" in state_return.comment + # It should not take 5 attempts + assert "Attempt 5" not in state_return.comment def test_state_non_base_environment(state, state_tree_prod, tmp_path): diff --git a/tests/pytests/integration/cli/test_salt_call.py b/tests/pytests/integration/cli/test_salt_call.py index f927f499c858..3948f620d108 100644 --- a/tests/pytests/integration/cli/test_salt_call.py +++ b/tests/pytests/integration/cli/test_salt_call.py @@ -81,6 +81,28 @@ def test_local_sls_call(salt_master, salt_call_cli): assert state_run_dict["changes"]["ret"] == "hello" +def test_local_sls_call_with_dict_args(salt_master, salt_call_cli): + sls_contents = """ + regular-module: + module.run: + name: test.echo + text: hello + """ + with salt_master.state_tree.base.temp_file("saltcalllocal.sls", sls_contents): + ret = salt_call_cli.run( + "--local", + "--file-root", + str(salt_master.state_tree.base.paths[0]), + "state.sls", + "saltcalllocal", + ) + assert ret.returncode == 0 + state_run_dict = next(iter(ret.data.values())) + assert state_run_dict["name"] == "test.echo" + assert state_run_dict["result"] is True + assert state_run_dict["changes"]["ret"] == "hello" + + def test_local_salt_call(salt_call_cli): """ This tests to make sure that salt-call does not execute the diff --git a/tests/pytests/integration/modules/test_state.py b/tests/pytests/integration/modules/test_state.py index 847a71df62c5..abd3cd853206 100644 --- a/tests/pytests/integration/modules/test_state.py +++ b/tests/pytests/integration/modules/test_state.py @@ -15,14 +15,12 @@ def test_logging_and_state_output_order(salt_master, salt_minion, salt_cli, tmp_ """ target_path = tmp_path / "file-target.txt" sls_name = "file-target" - sls_contents = """ + sls_contents = f""" add_contents_pillar_sls: file.managed: - - name: {} + - name: {target_path} - contents: foo - """.format( - target_path - ) + """ sls_tempfile = salt_master.state_tree.base.temp_file( f"{sls_name}.sls", sls_contents ) diff --git a/tests/pytests/integration/states/test_file.py b/tests/pytests/integration/states/test_file.py index d2a341910167..f298456e8865 100644 --- a/tests/pytests/integration/states/test_file.py +++ b/tests/pytests/integration/states/test_file.py @@ -1323,8 +1323,8 @@ def test_directory_recurse(salt_master, salt_call_cli, tmp_path, grains): with sls_tempfile: ret = salt_call_cli.run("state.sls", sls_name) key = f"file_|-{target_dir}_|-{target_dir}_|-directory" - assert key in ret.json - result = ret.json[key] + assert key in ret.data + result = ret.data[key] assert "changes" in result and result["changes"] # Permissions of file should not have changed. diff --git a/tests/pytests/pkg/integration/test_salt_call.py b/tests/pytests/pkg/integration/test_salt_call.py index c16ecb67481d..0be94e65b2e8 100644 --- a/tests/pytests/pkg/integration/test_salt_call.py +++ b/tests/pytests/pkg/integration/test_salt_call.py @@ -3,6 +3,9 @@ import pytest from pytestskipmarkers.utils import platform +import salt.version +from salt.utils.versions import Version + def test_salt_call_local(salt_call_cli): """ @@ -26,7 +29,7 @@ def test_salt_call(salt_call_cli, salt_master): @pytest.fixture def state_name(salt_master): - name = "some-test-state" + name = "state_name" sls_contents = """ test_foo: test.succeed_with_changes: @@ -46,13 +49,43 @@ def state_name(salt_master): yield name -def test_sls(salt_call_cli, salt_master, state_name): +@pytest.fixture +def state_name_dict_arg(salt_master): + name = "state_name_dict_arg" + sls_contents = """ + test_foo: + test.succeed_with_changes: + name: foo + """ + with salt_master.state_tree.base.temp_file(f"{name}.sls", sls_contents): + if not platform.is_windows() and not platform.is_darwin(): + subprocess.run( + [ + "chown", + "-R", + "salt:salt", + str(salt_master.state_tree.base.write_path), + ], + check=False, + ) + yield name + + +@pytest.mark.parametrize("fixture_name", ["state_name", "state_name_dict_arg"]) +def test_sls(salt_call_cli, salt_master, fixture_name, request): """ Test calling a sls file """ + min_version_required = Version("3008.0") + current_version = Version(salt.version.__version__) + if fixture_name == "state_name_dict_arg" and current_version < min_version_required: + pytest.skip( + f"requires Salt >= {min_version_required}, running {current_version}" + ) assert salt_master.is_running() + sls_id = request.getfixturevalue(fixture_name) - ret = salt_call_cli.run("state.apply", state_name) + ret = salt_call_cli.run("state.apply", sls_id) assert ret.returncode == 0 assert ret.data sls_ret = ret.data[next(iter(ret.data))] diff --git a/tests/pytests/unit/state/test_reactor_compiler.py b/tests/pytests/unit/state/test_reactor_compiler.py index 6e4a08019294..0e4451d51827 100644 --- a/tests/pytests/unit/state/test_reactor_compiler.py +++ b/tests/pytests/unit/state/test_reactor_compiler.py @@ -205,7 +205,7 @@ def test_compiler_pad_funcs_short_sls(minion_opts, tmp_path): } }, [ - "State 'master_pub' in SLS '/srv/reactor/start.sls' is not formed as a list" + "State 'master_pub' in SLS '/srv/reactor/start.sls' is not formed as a list or dict" ], ), ( diff --git a/tests/pytests/unit/state/test_state_basic.py b/tests/pytests/unit/state/test_state_basic.py index 00ef837f842e..2569f5497443 100644 --- a/tests/pytests/unit/state/test_state_basic.py +++ b/tests/pytests/unit/state/test_state_basic.py @@ -32,7 +32,7 @@ def test_state_args(): """ id_ = "/etc/bar.conf" state = "file" - high = OrderedDict( + high: salt.state.HighData = OrderedDict( [ ( "/etc/foo.conf", @@ -85,13 +85,60 @@ def test_state_args(): assert ret == {"order", "use"} +def test_state_args_as_dict() -> None: + """ + Testing state.state_args when this state is being used: + + /etc/foo.conf: + file.managed: + contents: "blah" + mkdirs: True + user: ch3ll + group: ch3ll + mode: 755 + + /etc/bar.conf: + file.managed: + use: + - file: /etc/foo.conf + """ + id_ = "/etc/bar.conf" + state = "file" + high: salt.state.HighData = { + "/etc/foo.conf": { + "file": { + "fun": "managed", + "contents": "blah", + "mkdirs": True, + "user": "ch3ll", + "group": "ch3ll", + "mode": 755, + "order": 10000, + }, + "__sls__": "test", + "__env__": "base", + }, + "/etc/bar.conf": { + "file": { + "fun": "managed", + "use": [{"file": "/etc/foo.conf"}], + "order": 10001, + }, + "__sls__": "test", + "__env__": "base", + }, + } + ret = salt.state.state_args(id_, state, high) + assert ret == {"order", "use"} + + def test_state_args_id_not_high(): """ Testing state.state_args when id_ is not in high """ id_ = "/etc/bar.conf2" state = "file" - high = OrderedDict( + high: salt.state.HighData = OrderedDict( [ ( "/etc/foo.conf", @@ -150,7 +197,7 @@ def test_state_args_state_not_high(): """ id_ = "/etc/bar.conf" state = "file2" - high = OrderedDict( + high: salt.state.HighData = OrderedDict( [ ( "/etc/foo.conf", diff --git a/tests/pytests/unit/state/test_state_compiler.py b/tests/pytests/unit/state/test_state_compiler.py index 122a12aaa769..32b5f731ccc1 100644 --- a/tests/pytests/unit/state/test_state_compiler.py +++ b/tests/pytests/unit/state/test_state_compiler.py @@ -720,10 +720,7 @@ def test_verify_retry_parsing(minion_opts): def test_render_requisite_require_disabled(minion_opts): - """ - Test that the state compiler correctly deliver a rendering - exception when a requisite cannot be resolved - """ + """Test disabling the require requisite via the options works""" with patch("salt.state.State._gather_pillar"): high_data = { "step_one": salt.state.HashableOrderedDict( @@ -817,6 +814,34 @@ def test_render_requisite_require_in_disabled(minion_opts): assert run_num == 0 +def test_render_with_dict_args(minion_opts): + """Test calling high state with state arguments specified as a dict""" + with patch("salt.state.State._gather_pillar"): + high_data: salt.state.HighData = { + "step_one": { + "test": { + "fun": "succeed_with_changes", + "require": [{"test": "step_two"}], + }, + "__sls__": "test.dict_args", + "__env__": "base", + }, + "step_two": { + "test": ["succeed_with_changes"], + "__env__": "base", + "__sls__": "test.dict_args", + }, + } + + state_obj = salt.state.State(minion_opts) + ret = state_obj.call_high(high_data) + assert isinstance(ret, dict) + run_num = ret["test_|-step_one_|-step_one_|-succeed_with_changes"][ + "__run_num__" + ] + assert run_num == 1 + + def test_call_chunk_sub_state_run(minion_opts): """ Test running a batch of states with an external runner