Merge 3006.x into master

This commit is contained in:
Pedro Algarvio 2023-09-20 15:52:18 +01:00
commit c07f66261e
No known key found for this signature in database
GPG key ID: BB36BF6584A298FF
59 changed files with 1274 additions and 236 deletions

View file

@ -110,6 +110,7 @@ jobs:
DEV_APP_CERT: "${{ secrets.MAC_SIGN_DEV_APP_CERT }}"
DEV_INSTALL_CERT: "${{ secrets.MAC_SIGN_DEV_INSTALL_CERT }}"
APPLE_ACCT: "${{ secrets.MAC_SIGN_APPLE_ACCT }}"
APPLE_TEAM_ID: "${{ secrets.MAC_SIGN_APPLE_TEAM_ID }}"
APP_SPEC_PWD: "${{ secrets.MAC_SIGN_APP_SPEC_PWD }}"
run: |
tools pkg build macos --relenv-version=${{ inputs.relenv-version }} --python-version=${{ inputs.python-version }} ${{

View file

@ -578,6 +578,9 @@ commit message, it's usually a good idea to add other information, such as
This will also help you out, because when you go to create the PR it
will automatically insert the body of your commit messages.
See the `changelog <https://docs.saltproject.io/en/latest/topics/development/changelog.html>`__
docs for more information.
Pull request time!
------------------

View file

@ -121,7 +121,7 @@ Security advisories
Keep an eye on the Salt Project
`Security Announcements <https://saltproject.io/security-announcements/>`_
landing page. Salt Project recommends subscribing to the
`Salt Project Security RSS feed <https://saltproject.io/feed/?post_type=security>`_
`Salt Project Security RSS feed <https://saltproject.io/security-announcements/index.xml>`_
to receive notification when new information is available regarding security
announcements.

1
changelog/65093.fixed.md Normal file
View file

@ -0,0 +1 @@
Only attempt to create a keys directory when `--gen-keys` is passed to the `salt-key` CLI

1
changelog/65179.fixed.md Normal file
View file

@ -0,0 +1 @@
Ensure __kwarg__ is preserved when checking for kwargs. This change affects proxy minions when used with Deltaproxy, which had kwargs popped when targeting multiple minions id.

1
changelog/65210.fixed.md Normal file
View file

@ -0,0 +1 @@
Fixes traceback when state id is an int in a reactor SLS file.

View file

@ -197,3 +197,12 @@ How to access python binary
The python library is available in the install directory of the onedir package. For example
on linux the default location would be ``/opt/saltstack/salt/bin/python3``.
Testing the packages
====================
If you want to test your built packages, or any other collection of salt packages post 3006.0, follow :ref:`this guide <pkging-testing>`
.. toctree::
testing

View file

@ -0,0 +1,157 @@
.. _pkging-testing:
================
Testing packages
================
The package test suite
======================
The salt repo provides a test suite for testing basic functionality of our
packages at ``<repo-root>/pkg/tests/``. You can run the install, upgrade, and
downgrade tests. These tests run automatically on most PRs that are submitted
against Salt.
.. warning::
These tests make destructive changes to your system because they install the
built packages onto the system. They may also install older versions in the
case of upgrades or downgrades. To prevent destructive changes, run the
tests in an isolated system, preferably a virtual machine.
Setup
=====
In order to run the package tests, the `relenv
<https://github.com/saltstack/relative-environment-for-python>`_ onedir and
built packages need to be placed in the correct locations.
* Place all salt packages for the applicable testing version in
``<repo-root>/pkg/artifacts/``.
* The onedir must be located under ``<repo-root>/artifacts/``.
* Additionally, to ensure complete parity with Salt's CI/CD suite, place the
``nox`` virtual environment in ``<repo-root>/.nox/test-pkgs-onedir``.
The following are a few ways this can be accomplished easily.
You can ensure parity by installing the package test suite through a few
possible methods:
* Using ``tools``
* Downloading individually
Using ``tools``
---------------
Salt has preliminary support for setting up the package test suite in the
``tools`` command suite that is located under ``<repo-root>/tools/testsuite/``.
This method requires the Github CLI tool ``gh`` (https://cli.github.com/) to be properly configured for
interaction with the salt repo.
#. Install the dependencies using this command:
.. code-block:: bash
pip install -r requirements/static/ci/py{python_version}/tools.txt
#. Download and extract the artifacts with this ``tools`` command:
.. code-block:: bash
tools ts setup --platform {linux|darwin|windows} --slug
<operating-system-slug> --pr <pr-number> --pkg
The most common use case is to test the packages built on a CI/CD run for a
given PR. To see the possible options for each argument, and other ways to
utilize this command, use the following:
.. code-block:: bash
tools ts setup -h
.. warning::
You can only download artifacts from finished workflow runs. This is something
imposed by the GitHub API.
To download artifacts from a running workflow run, you either have to wait for
the finish or cancel it.
Downloading individually
------------------------
If the ``tools ts setup`` command doesn't work, you can download, unzip, and
place the artifacts in the correct locations manually. Typically, you want to
test packages built on a CI/CD run for a given PR. This guide explains how to
set up for running the package tests using those artifacts. An analogous process
can be performed for artifacts from nightly builds.
#. Find and download the artifacts:
Under the summary page for the most recent actions run for that PR, there is
a list of available artifacts from that run that can be downloaded. Download
the package artifacts by finding
``salt-<major>.<minor>+<number>.<sha>-<arch>-<pkg-type>``. For example, the
amd64 deb packages might look like:
``salt-3006.2+123.01234567890-x86_64-deb``.
The onedir artifact will look like
``salt-<major>.<minor>+<number>.<sha>-onedir-<platform>-<arch>.tar.xz``. For
instance, the macos x86_64 onedir may have the name
``salt-3006.2+123.01234567890-onedir-darwin-x86_64.tar.xz``.
.. note::
Windows onedir artifacts have ``.zip`` extensions instead of ``tar.xz``
While it is optional, it is recommended to download the ``nox`` session
artifact as well. This will have the form of
``nox-<os-name>-test-pkgs-onedir-<arch>``. The amd64 Ubuntu 20.04 nox
artifact may look like ``nox-ubuntu-20.04-test-pkgs-onedir-x86_64``.
#. Place the artifacts in the correct location:
Unzip the packages and place them in ``<repo-root>/pkg/artifacts/``.
You must unzip and untar the onedir packages and place them in
``<repo-root>/artifacts/``. Windows onedir requires an additional unzip
action. If you set it up correctly, the ``<repo-root>/artifacts/salt``
directory then contains the uncompressed onedir files.
Additionally, decompress the ``nox`` artifact and place it under
``<repo-root>/.nox/``.
Running the tests
=================
You can run the test suite run if all the artifacts are in the correct location.
.. note::
You need root access to run the test artifacts. Run all nox commands at the
root of the salt repo and as the root user.
#. Install ``nox``:
.. code-block:: bash
pip install nox
#. Run the install tests:
.. code-block:: bash
nox -e test-pkgs-onedir -- install
#. Run the upgrade or downgrade tests:
.. code-block:: bash
nox -e test-pkgs-onedir -- upgrade --prev-version <previous-version>
You can run the downgrade tests in the same way, replacing ``upgrade`` with
``downgrade``.
.. note::
If you are testing upgrades or downgrades and classic packages are
available for your system, replace ``upgrade`` or
``downgrade`` with ``upgrade-classic`` or ``downgrade-classic``
respectively to test against those versions.

View file

@ -53,10 +53,7 @@ def test_salt_downgrade(salt_call_cli, install_salt):
ret.stdout.strip().split()[1]
) < packaging.version.parse(install_salt.artifact_version)
# Windows does not keep the extras directory around in the same state
# TODO: Fix this problem in windows installers
# TODO: Fix this problem in macos installers
if is_downgrade_to_relenv and not (platform.is_windows() or platform.is_darwin()):
if is_downgrade_to_relenv:
new_py_version = install_salt.package_python_version()
if new_py_version == original_py_version:
# test pip install after a downgrade

View file

@ -272,7 +272,7 @@ netaddr==0.8.0
# pyeapi
netmiko==4.2.0
# via napalm
netutils==1.4.1
netutils==1.6.0
# via napalm
ntc-templates==3.4.0
# via netmiko
@ -485,7 +485,7 @@ sqlparse==0.4.4
# via -r requirements/static/ci/common.in
strict-rfc3339==0.7
# via -r requirements/static/ci/common.in
tempora==5.2.2
tempora==5.3.0
# via
# -c requirements/static/ci/../pkg/py3.10/darwin.txt
# portend
@ -510,13 +510,13 @@ transitions==0.9.0
# via junos-eznc
ttp-templates==0.3.5
# via napalm
ttp==0.9.4
ttp==0.9.5
# via
# napalm
# ttp-templates
types-pyyaml==6.0.1
# via responses
typing-extensions==4.6.2
typing-extensions==4.6.3
# via
# -c requirements/static/ci/../pkg/py3.10/darwin.txt
# napalm

View file

@ -142,11 +142,11 @@ sphinxcontrib-serializinghtml==1.1.5
# via sphinx
sphinxcontrib-spelling==8.0.0
# via -r requirements/static/ci/docs.in
tempora==5.2.2
tempora==5.3.0
# via
# -c requirements/static/ci/py3.10/linux.txt
# portend
typing-extensions==4.6.2
typing-extensions==4.6.3
# via
# -c requirements/static/ci/py3.10/linux.txt
# pydantic

View file

@ -267,7 +267,7 @@ netaddr==0.8.0
# pyeapi
netmiko==4.2.0
# via napalm
netutils==1.4.1
netutils==1.6.0
# via napalm
ntc-templates==3.4.0
# via netmiko
@ -477,7 +477,7 @@ sqlparse==0.4.4
# via -r requirements/static/ci/common.in
strict-rfc3339==0.7
# via -r requirements/static/ci/common.in
tempora==5.2.2
tempora==5.3.0
# via
# -c requirements/static/ci/../pkg/py3.10/freebsd.txt
# portend
@ -502,13 +502,13 @@ transitions==0.9.0
# via junos-eznc
ttp-templates==0.3.5
# via napalm
ttp==0.9.4
ttp==0.9.5
# via
# napalm
# ttp-templates
types-pyyaml==6.0.1
# via responses
typing-extensions==4.6.2
typing-extensions==4.6.3
# via
# -c requirements/static/ci/../pkg/py3.10/freebsd.txt
# napalm

View file

@ -292,7 +292,7 @@ netaddr==0.8.0
# pyeapi
netmiko==4.2.0
# via napalm
netutils==1.4.1
netutils==1.6.0
# via napalm
ntc-templates==3.4.0
# via netmiko
@ -539,7 +539,7 @@ sqlparse==0.4.4
# via -r requirements/static/ci/common.in
strict-rfc3339==0.7
# via -r requirements/static/ci/common.in
tempora==5.2.2
tempora==5.3.0
# via
# -c requirements/static/ci/../pkg/py3.10/linux.txt
# portend
@ -548,7 +548,7 @@ textfsm==1.1.3
# napalm
# netmiko
# ntc-templates
timelib==0.2.5
timelib==0.3.0
# via
# -c requirements/static/ci/../pkg/py3.10/linux.txt
# -r requirements/static/pkg/linux.in
@ -564,7 +564,7 @@ transitions==0.9.0
# via junos-eznc
ttp-templates==0.3.5
# via napalm
ttp==0.9.4
ttp==0.9.5
# via
# napalm
# ttp-templates
@ -572,7 +572,7 @@ twilio==8.2.2
# via -r requirements/static/ci/linux.in
types-pyyaml==6.0.1
# via responses
typing-extensions==4.6.2
typing-extensions==4.6.3
# via
# -c requirements/static/ci/../pkg/py3.10/linux.txt
# napalm

View file

@ -109,7 +109,7 @@ pytest-helpers-namespace==2021.12.29
# pytest-shell-utilities
pytest-salt-factories==1.0.0rc17
# via -r requirements/static/ci/pkgtests-windows.in
pytest-shell-utilities==1.7.0
pytest-shell-utilities==1.8.0
# via pytest-salt-factories
pytest-skip-markers==1.4.1
# via

View file

@ -106,7 +106,7 @@ pytest-helpers-namespace==2021.12.29
# pytest-shell-utilities
pytest-salt-factories==1.0.0rc17
# via -r requirements/static/ci/pkgtests.in
pytest-shell-utilities==1.7.0
pytest-shell-utilities==1.8.0
# via pytest-salt-factories
pytest-skip-markers==1.4.1
# via

View file

@ -441,7 +441,7 @@ sqlparse==0.4.4
# via -r requirements/static/ci/common.in
strict-rfc3339==0.7
# via -r requirements/static/ci/common.in
tempora==5.2.2
tempora==5.3.0
# via
# -c requirements/static/ci/../pkg/py3.10/windows.txt
# portend
@ -459,7 +459,7 @@ tornado==6.3.2
# -r requirements/base.txt
types-pyyaml==6.0.1
# via responses
typing-extensions==4.6.2
typing-extensions==4.6.3
# via
# -c requirements/static/ci/../pkg/py3.10/windows.txt
# pydantic

View file

@ -270,7 +270,7 @@ netaddr==0.8.0
# pyeapi
netmiko==4.2.0
# via napalm
netutils==1.4.1
netutils==1.6.0
# via napalm
ntc-templates==3.4.0
# via netmiko
@ -483,7 +483,7 @@ sqlparse==0.4.4
# via -r requirements/static/ci/common.in
strict-rfc3339==0.7
# via -r requirements/static/ci/common.in
tempora==5.2.2
tempora==5.3.0
# via
# -c requirements/static/ci/../pkg/py3.11/darwin.txt
# portend
@ -506,13 +506,13 @@ transitions==0.9.0
# via junos-eznc
ttp-templates==0.3.5
# via napalm
ttp==0.9.4
ttp==0.9.5
# via
# napalm
# ttp-templates
types-pyyaml==6.0.1
# via responses
typing-extensions==4.6.2
typing-extensions==4.6.3
# via
# -c requirements/static/ci/../pkg/py3.11/darwin.txt
# napalm

View file

@ -142,11 +142,11 @@ sphinxcontrib-serializinghtml==1.1.5
# via sphinx
sphinxcontrib-spelling==8.0.0
# via -r requirements/static/ci/docs.in
tempora==5.2.2
tempora==5.3.0
# via
# -c requirements/static/ci/py3.11/linux.txt
# portend
typing-extensions==4.6.2
typing-extensions==4.6.3
# via
# -c requirements/static/ci/py3.11/linux.txt
# pydantic

View file

@ -265,7 +265,7 @@ netaddr==0.8.0
# pyeapi
netmiko==4.2.0
# via napalm
netutils==1.4.1
netutils==1.6.0
# via napalm
ntc-templates==3.4.0
# via netmiko
@ -475,7 +475,7 @@ sqlparse==0.4.4
# via -r requirements/static/ci/common.in
strict-rfc3339==0.7
# via -r requirements/static/ci/common.in
tempora==5.2.2
tempora==5.3.0
# via
# -c requirements/static/ci/../pkg/py3.11/freebsd.txt
# portend
@ -498,13 +498,13 @@ transitions==0.9.0
# via junos-eznc
ttp-templates==0.3.5
# via napalm
ttp==0.9.4
ttp==0.9.5
# via
# napalm
# ttp-templates
types-pyyaml==6.0.1
# via responses
typing-extensions==4.6.2
typing-extensions==4.6.3
# via
# -c requirements/static/ci/../pkg/py3.11/freebsd.txt
# napalm

View file

@ -288,7 +288,7 @@ netaddr==0.8.0
# pyeapi
netmiko==4.2.0
# via napalm
netutils==1.4.1
netutils==1.6.0
# via napalm
ntc-templates==3.4.0
# via netmiko
@ -535,7 +535,7 @@ sqlparse==0.4.4
# via -r requirements/static/ci/common.in
strict-rfc3339==0.7
# via -r requirements/static/ci/common.in
tempora==5.2.2
tempora==5.3.0
# via
# -c requirements/static/ci/../pkg/py3.11/linux.txt
# portend
@ -558,7 +558,7 @@ transitions==0.9.0
# via junos-eznc
ttp-templates==0.3.5
# via napalm
ttp==0.9.4
ttp==0.9.5
# via
# napalm
# ttp-templates
@ -566,7 +566,7 @@ twilio==8.2.2
# via -r requirements/static/ci/linux.in
types-pyyaml==6.0.1
# via responses
typing-extensions==4.6.2
typing-extensions==4.6.3
# via
# -c requirements/static/ci/../pkg/py3.11/linux.txt
# napalm

View file

@ -142,11 +142,11 @@ pyzmq==25.1.0
# pytest-salt-factories
requests==2.31.0
# via -r requirements/base.txt
tempora==5.2.2
tempora==5.3.0
# via portend
tornado==6.3.2
# via -r requirements/base.txt
typing-extensions==4.6.2
typing-extensions==4.6.3
# via
# pydantic
# pytest-shell-utilities

View file

@ -135,11 +135,11 @@ requests==2.31.0
# via
# -r requirements/base.txt
# docker
tempora==5.2.2
tempora==5.3.0
# via portend
tornado==6.3.2
# via -r requirements/base.txt
typing-extensions==4.6.2
typing-extensions==4.6.3
# via
# pydantic
# pytest-shell-utilities

View file

@ -439,7 +439,7 @@ sqlparse==0.4.4
# via -r requirements/static/ci/common.in
strict-rfc3339==0.7
# via -r requirements/static/ci/common.in
tempora==5.2.2
tempora==5.3.0
# via
# -c requirements/static/ci/../pkg/py3.11/windows.txt
# portend
@ -455,7 +455,7 @@ tornado==6.3.2
# -r requirements/base.txt
types-pyyaml==6.0.1
# via responses
typing-extensions==4.6.2
typing-extensions==4.6.3
# via
# -c requirements/static/ci/../pkg/py3.11/windows.txt
# pydantic

View file

@ -151,11 +151,11 @@ sphinxcontrib-serializinghtml==1.1.5
# via sphinx
sphinxcontrib-spelling==8.0.0
# via -r requirements/static/ci/docs.in
tempora==5.2.2
tempora==5.3.0
# via
# -c requirements/static/ci/py3.8/linux.txt
# portend
typing-extensions==4.6.2
typing-extensions==4.6.3
# via
# -c requirements/static/ci/py3.8/linux.txt
# pydantic

View file

@ -481,7 +481,7 @@ sqlparse==0.4.4
# via -r requirements/static/ci/common.in
strict-rfc3339==0.7
# via -r requirements/static/ci/common.in
tempora==5.2.2
tempora==5.3.0
# via
# -c requirements/static/ci/../pkg/py3.8/freebsd.txt
# portend
@ -512,7 +512,7 @@ ttp==0.9.5
# ttp-templates
types-pyyaml==6.0.1
# via responses
typing-extensions==4.6.2
typing-extensions==4.6.3
# via
# -c requirements/static/ci/../pkg/py3.8/freebsd.txt
# napalm

View file

@ -543,7 +543,7 @@ sqlparse==0.4.4
# via -r requirements/static/ci/common.in
strict-rfc3339==0.7
# via -r requirements/static/ci/common.in
tempora==5.2.2
tempora==5.3.0
# via
# -c requirements/static/ci/../pkg/py3.8/linux.txt
# portend
@ -576,7 +576,7 @@ twilio==8.2.2
# via -r requirements/static/ci/linux.in
types-pyyaml==6.0.1
# via responses
typing-extensions==4.6.2
typing-extensions==4.6.3
# via
# -c requirements/static/ci/../pkg/py3.8/linux.txt
# napalm

View file

@ -446,7 +446,7 @@ sqlparse==0.4.4
# via -r requirements/static/ci/common.in
strict-rfc3339==0.7
# via -r requirements/static/ci/common.in
tempora==5.2.2
tempora==5.3.0
# via
# -c requirements/static/ci/../pkg/py3.8/windows.txt
# portend
@ -464,7 +464,7 @@ tornado==6.3.2
# -r requirements/base.txt
types-pyyaml==6.0.1
# via responses
typing-extensions==4.6.2
typing-extensions==4.6.3
# via
# -c requirements/static/ci/../pkg/py3.8/windows.txt
# pydantic

View file

@ -485,7 +485,7 @@ sqlparse==0.4.4
# via -r requirements/static/ci/common.in
strict-rfc3339==0.7
# via -r requirements/static/ci/common.in
tempora==5.2.2
tempora==5.3.0
# via
# -c requirements/static/ci/../pkg/py3.9/darwin.txt
# portend
@ -516,7 +516,7 @@ ttp==0.9.5
# ttp-templates
types-pyyaml==6.0.1
# via responses
typing-extensions==4.6.2
typing-extensions==4.6.3
# via
# -c requirements/static/ci/../pkg/py3.9/darwin.txt
# napalm

View file

@ -146,11 +146,11 @@ sphinxcontrib-serializinghtml==1.1.5
# via sphinx
sphinxcontrib-spelling==8.0.0
# via -r requirements/static/ci/docs.in
tempora==5.2.2
tempora==5.3.0
# via
# -c requirements/static/ci/py3.9/linux.txt
# portend
typing-extensions==4.6.2
typing-extensions==4.6.3
# via
# -c requirements/static/ci/py3.9/linux.txt
# pydantic

View file

@ -477,7 +477,7 @@ sqlparse==0.4.4
# via -r requirements/static/ci/common.in
strict-rfc3339==0.7
# via -r requirements/static/ci/common.in
tempora==5.2.2
tempora==5.3.0
# via
# -c requirements/static/ci/../pkg/py3.9/freebsd.txt
# portend
@ -508,7 +508,7 @@ ttp==0.9.5
# ttp-templates
types-pyyaml==6.0.1
# via responses
typing-extensions==4.6.2
typing-extensions==4.6.3
# via
# -c requirements/static/ci/../pkg/py3.9/freebsd.txt
# napalm

View file

@ -541,7 +541,7 @@ sqlparse==0.4.4
# via -r requirements/static/ci/common.in
strict-rfc3339==0.7
# via -r requirements/static/ci/common.in
tempora==5.2.2
tempora==5.3.0
# via
# -c requirements/static/ci/../pkg/py3.9/linux.txt
# portend
@ -574,7 +574,7 @@ twilio==8.2.2
# via -r requirements/static/ci/linux.in
types-pyyaml==6.0.1
# via responses
typing-extensions==4.6.2
typing-extensions==4.6.3
# via
# -c requirements/static/ci/../pkg/py3.9/linux.txt
# napalm

View file

@ -442,7 +442,7 @@ sqlparse==0.4.4
# via -r requirements/static/ci/common.in
strict-rfc3339==0.7
# via -r requirements/static/ci/common.in
tempora==5.2.2
tempora==5.3.0
# via
# -c requirements/static/ci/../pkg/py3.9/windows.txt
# portend
@ -460,7 +460,7 @@ tornado==6.3.2
# -r requirements/base.txt
types-pyyaml==6.0.1
# via responses
typing-extensions==4.6.2
typing-extensions==4.6.3
# via
# -c requirements/static/ci/../pkg/py3.9/windows.txt
# pydantic

View file

@ -107,13 +107,13 @@ six==1.16.0
# via python-dateutil
smmap==3.0.2
# via gitdb
tempora==5.2.2
tempora==5.3.0
# via portend
timelib==0.3.0
# via -r requirements/darwin.txt
tornado==6.3.2
# via -r requirements/base.txt
typing-extensions==4.6.2
typing-extensions==4.6.3
# via pydantic
urllib3==1.26.6
# via requests

View file

@ -94,13 +94,13 @@ setproctitle==1.3.2
# via -r requirements/static/pkg/freebsd.in
six==1.16.0
# via python-dateutil
tempora==5.2.2
tempora==5.3.0
# via portend
timelib==0.3.0
# via -r requirements/static/pkg/freebsd.in
tornado==6.3.2
# via -r requirements/base.txt
typing-extensions==4.6.2
typing-extensions==4.6.3
# via pydantic
urllib3==1.26.6
# via requests

View file

@ -94,13 +94,13 @@ setproctitle==1.3.2
# via -r requirements/static/pkg/linux.in
six==1.16.0
# via python-dateutil
tempora==5.2.2
tempora==5.3.0
# via portend
timelib==0.2.5
timelib==0.3.0
# via -r requirements/static/pkg/linux.in
tornado==6.3.2
# via -r requirements/base.txt
typing-extensions==4.6.2
typing-extensions==4.6.3
# via pydantic
urllib3==1.26.6
# via requests

View file

@ -121,13 +121,13 @@ six==1.15.0
# via python-dateutil
smmap==4.0.0
# via gitdb
tempora==5.2.2
tempora==5.3.0
# via portend
timelib==0.3.0
# via -r requirements/windows.txt
tornado==6.3.2
# via -r requirements/base.txt
typing-extensions==4.6.2
typing-extensions==4.6.3
# via pydantic
urllib3==1.26.6
# via

View file

@ -107,13 +107,13 @@ six==1.16.0
# via python-dateutil
smmap==3.0.2
# via gitdb
tempora==5.2.2
tempora==5.3.0
# via portend
timelib==0.3.0
# via -r requirements/darwin.txt
tornado==6.3.2
# via -r requirements/base.txt
typing-extensions==4.6.2
typing-extensions==4.6.3
# via pydantic
urllib3==1.26.6
# via requests

View file

@ -94,13 +94,13 @@ setproctitle==1.3.2
# via -r requirements/static/pkg/freebsd.in
six==1.16.0
# via python-dateutil
tempora==5.2.2
tempora==5.3.0
# via portend
timelib==0.3.0
# via -r requirements/static/pkg/freebsd.in
tornado==6.3.2
# via -r requirements/base.txt
typing-extensions==4.6.2
typing-extensions==4.6.3
# via pydantic
urllib3==1.26.6
# via requests

View file

@ -94,13 +94,13 @@ setproctitle==1.3.2
# via -r requirements/static/pkg/linux.in
six==1.16.0
# via python-dateutil
tempora==5.2.2
tempora==5.3.0
# via portend
timelib==0.3.0
# via -r requirements/static/pkg/linux.in
tornado==6.3.2
# via -r requirements/base.txt
typing-extensions==4.6.2
typing-extensions==4.6.3
# via pydantic
urllib3==1.26.6
# via requests

View file

@ -121,13 +121,13 @@ six==1.15.0
# via python-dateutil
smmap==4.0.0
# via gitdb
tempora==5.2.2
tempora==5.3.0
# via portend
timelib==0.3.0
# via -r requirements/windows.txt
tornado==6.3.2
# via -r requirements/base.txt
typing-extensions==4.6.2
typing-extensions==4.6.3
# via pydantic
urllib3==1.26.6
# via

View file

@ -96,13 +96,13 @@ setproctitle==1.3.2
# via -r requirements/static/pkg/freebsd.in
six==1.16.0
# via python-dateutil
tempora==5.2.2
tempora==5.3.0
# via portend
timelib==0.3.0
# via -r requirements/static/pkg/freebsd.in
tornado==6.3.2
# via -r requirements/base.txt
typing-extensions==4.6.2
typing-extensions==4.6.3
# via pydantic
urllib3==1.26.6
# via requests

View file

@ -96,13 +96,13 @@ setproctitle==1.3.2
# via -r requirements/static/pkg/linux.in
six==1.16.0
# via python-dateutil
tempora==5.2.2
tempora==5.3.0
# via portend
timelib==0.3.0
# via -r requirements/static/pkg/linux.in
tornado==6.3.2
# via -r requirements/base.txt
typing-extensions==4.6.2
typing-extensions==4.6.3
# via pydantic
urllib3==1.26.6
# via requests

View file

@ -124,13 +124,13 @@ six==1.15.0
# via python-dateutil
smmap==4.0.0
# via gitdb
tempora==5.2.2
tempora==5.3.0
# via portend
timelib==0.3.0
# via -r requirements/windows.txt
tornado==6.3.2
# via -r requirements/base.txt
typing-extensions==4.6.2
typing-extensions==4.6.3
# via pydantic
urllib3==1.26.6
# via

View file

@ -107,13 +107,13 @@ six==1.16.0
# via python-dateutil
smmap==3.0.2
# via gitdb
tempora==5.2.2
tempora==5.3.0
# via portend
timelib==0.3.0
# via -r requirements/darwin.txt
tornado==6.3.2
# via -r requirements/base.txt
typing-extensions==4.6.2
typing-extensions==4.6.3
# via pydantic
urllib3==1.26.6
# via requests

View file

@ -94,13 +94,13 @@ setproctitle==1.3.2
# via -r requirements/static/pkg/freebsd.in
six==1.16.0
# via python-dateutil
tempora==5.2.2
tempora==5.3.0
# via portend
timelib==0.3.0
# via -r requirements/static/pkg/freebsd.in
tornado==6.3.2
# via -r requirements/base.txt
typing-extensions==4.6.2
typing-extensions==4.6.3
# via pydantic
urllib3==1.26.6
# via requests

View file

@ -94,13 +94,13 @@ setproctitle==1.3.2
# via -r requirements/static/pkg/linux.in
six==1.16.0
# via python-dateutil
tempora==5.2.2
tempora==5.3.0
# via portend
timelib==0.3.0
# via -r requirements/static/pkg/linux.in
tornado==6.3.2
# via -r requirements/base.txt
typing-extensions==4.6.2
typing-extensions==4.6.3
# via pydantic
urllib3==1.26.6
# via requests

View file

@ -122,13 +122,13 @@ six==1.15.0
# via python-dateutil
smmap==4.0.0
# via gitdb
tempora==5.2.2
tempora==5.3.0
# via portend
timelib==0.3.0
# via -r requirements/windows.txt
tornado==6.3.2
# via -r requirements/base.txt
typing-extensions==4.6.2
typing-extensions==4.6.3
# via pydantic
urllib3==1.26.6
# via

View file

@ -328,9 +328,12 @@ def load_args_and_kwargs(func, args, data=None, ignore_invalid=False):
invalid_kwargs = []
for arg in args:
if isinstance(arg, dict) and arg.pop("__kwarg__", False) is True:
if isinstance(arg, dict) and arg.get("__kwarg__", False) is True:
# if the arg is a dict with __kwarg__ == True, then its a kwarg
for key, val in arg.items():
# Skip __kwarg__ when checking kwargs
if key == "__kwarg__":
continue
if argspec.keywords or key in argspec.args:
# Function supports **kwargs or is a positional argument to
# the function.

View file

@ -449,145 +449,157 @@ class Compiler:
if not isinstance(high, dict):
errors.append("High data is not a dictionary and is invalid")
reqs = OrderedDict()
for name, body in high.items():
if name.startswith("__"):
continue
if not isinstance(name, str):
errors.append(
"ID '{}' in SLS '{}' is not formed as a string, but is a {}".format(
name, body["__sls__"], type(name).__name__
)
)
if not isinstance(body, dict):
err = "The type {} in {} is not formatted as a dictionary".format(
name, body
)
errors.append(err)
continue
for state in body:
if state.startswith("__"):
continue
if not isinstance(body[state], list):
if not errors:
for name, body in high.items():
try:
if name.startswith("__"):
continue
except (AttributeError, TypeError):
# Do not traceback on non string state ID
# handle the error properly
pass
if not isinstance(name, str):
errors.append(
"State '{}' in SLS '{}' is not formed as a list".format(
name, body["__sls__"]
"ID '{}' in SLS '{}' is not formed as a string, but is a {}. It may need to be quoted".format(
name, body["__sls__"], type(name).__name__
)
)
else:
fun = 0
if "." in state:
fun += 1
for arg in body[state]:
if isinstance(arg, str):
fun += 1
if " " in arg.strip():
errors.append(
'The function "{}" in state '
'"{}" in SLS "{}" has '
"whitespace, a function with whitespace is "
"not supported, perhaps this is an argument "
'that is missing a ":"'.format(
arg, name, body["__sls__"]
)
)
elif isinstance(arg, dict):
# The arg is a dict, if the arg is require or
# watch, it must be a list.
#
# Add the requires to the reqs dict and check them
# all for recursive requisites.
argfirst = next(iter(arg))
if argfirst in ("require", "watch", "prereq", "onchanges"):
if not isinstance(arg[argfirst], list):
errors.append(
"The {} statement in state '{}' in SLS '{}' "
"needs to be formed as a list".format(
argfirst, name, body["__sls__"]
)
)
# It is a list, verify that the members of the
# list are all single key dicts.
else:
reqs[name] = {"state": state}
for req in arg[argfirst]:
if isinstance(req, str):
req = {"id": req}
if not isinstance(req, dict):
errors.append(
"Requisite declaration {} in SLS {} "
"is not formed as a single key "
"dictionary".format(
req, body["__sls__"]
)
)
continue
req_key = next(iter(req))
req_val = req[req_key]
if "." in req_key:
errors.append(
"Invalid requisite type '{}' "
"in state '{}', in SLS "
"'{}'. Requisite types must "
"not contain dots, did you "
"mean '{}'?".format(
req_key,
name,
body["__sls__"],
req_key[: req_key.find(".")],
)
)
if not ishashable(req_val):
errors.append(
'Illegal requisite "{}", is SLS {}\n'.format(
str(req_val),
body["__sls__"],
)
)
continue
# Check for global recursive requisites
reqs[name][req_val] = req_key
# I am going beyond 80 chars on
# purpose, this is just too much
# of a pain to deal with otherwise
if req_val in reqs:
if name in reqs[req_val]:
if reqs[req_val][name] == state:
if (
reqs[req_val]["state"]
== reqs[name][req_val]
):
errors.append(
"A recursive requisite was"
' found, SLS "{}" ID "{}"'
' ID "{}"'.format(
body["__sls__"],
name,
req_val,
)
)
# Make sure that there is only one key in the
# dict
if len(list(arg)) != 1:
errors.append(
"Multiple dictionaries defined in argument "
"of state '{}' in SLS '{}'".format(
name, body["__sls__"]
)
)
if not fun:
if state == "require" or state == "watch":
continue
if not isinstance(body, dict):
err = "The type {} in {} is not formatted as a dictionary".format(
name, body
)
errors.append(err)
continue
for state in body:
if state.startswith("__"):
continue
if not isinstance(body[state], list):
errors.append(
"No function declared in state '{}' in SLS '{}'".format(
state, body["__sls__"]
"State '{}' in SLS '{}' is not formed as a list".format(
name, body["__sls__"]
)
)
elif fun > 1:
errors.append(
"Too many functions declared in state '{}' in "
"SLS '{}'".format(state, body["__sls__"])
)
else:
fun = 0
if "." in state:
fun += 1
for arg in body[state]:
if isinstance(arg, str):
fun += 1
if " " in arg.strip():
errors.append(
'The function "{}" in state '
'"{}" in SLS "{}" has '
"whitespace, a function with whitespace is "
"not supported, perhaps this is an argument "
'that is missing a ":"'.format(
arg, name, body["__sls__"]
)
)
elif isinstance(arg, dict):
# The arg is a dict, if the arg is require or
# watch, it must be a list.
#
# Add the requires to the reqs dict and check them
# all for recursive requisites.
argfirst = next(iter(arg))
if argfirst in (
"require",
"watch",
"prereq",
"onchanges",
):
if not isinstance(arg[argfirst], list):
errors.append(
"The {} statement in state '{}' in SLS '{}' "
"needs to be formed as a list".format(
argfirst, name, body["__sls__"]
)
)
# It is a list, verify that the members of the
# list are all single key dicts.
else:
reqs[name] = {"state": state}
for req in arg[argfirst]:
if isinstance(req, str):
req = {"id": req}
if not isinstance(req, dict):
errors.append(
"Requisite declaration {} in SLS {} "
"is not formed as a single key "
"dictionary".format(
req, body["__sls__"]
)
)
continue
req_key = next(iter(req))
req_val = req[req_key]
if "." in req_key:
errors.append(
"Invalid requisite type '{}' "
"in state '{}', in SLS "
"'{}'. Requisite types must "
"not contain dots, did you "
"mean '{}'?".format(
req_key,
name,
body["__sls__"],
req_key[: req_key.find(".")],
)
)
if not ishashable(req_val):
errors.append(
'Illegal requisite "{}", is SLS {}\n'.format(
str(req_val),
body["__sls__"],
)
)
continue
# Check for global recursive requisites
reqs[name][req_val] = req_key
# I am going beyond 80 chars on
# purpose, this is just too much
# of a pain to deal with otherwise
if req_val in reqs:
if name in reqs[req_val]:
if reqs[req_val][name] == state:
if (
reqs[req_val]["state"]
== reqs[name][req_val]
):
errors.append(
"A recursive requisite was"
' found, SLS "{}" ID "{}"'
' ID "{}"'.format(
body["__sls__"],
name,
req_val,
)
)
# Make sure that there is only one key in the
# dict
if len(list(arg)) != 1:
errors.append(
"Multiple dictionaries defined in argument "
"of state '{}' in SLS '{}'".format(
name, body["__sls__"]
)
)
if not fun:
if state == "require" or state == "watch":
continue
errors.append(
"No function declared in state '{}' in SLS '{}'".format(
state, body["__sls__"]
)
)
elif fun > 1:
errors.append(
"Too many functions declared in state '{}' in "
"SLS '{}'".format(state, body["__sls__"])
)
return errors
def order_chunks(self, chunks):

View file

@ -101,6 +101,9 @@ def present(
docker_image.present:
- tag: mytag
name
The name of the docker image.
tag
Tag name for the image. Required when using ``build``, ``load``, or
``sls`` to create the image, but optional if pulling from a repository.
@ -146,10 +149,14 @@ def present(
.. versionchanged:: 2018.3.0
The ``tag`` must be manually specified using the ``tag`` argument.
force : False
force
Set this parameter to ``True`` to force Salt to pull/build/load the
image even if it is already present.
insecure_registry
If ``True``, the Docker client will permit the use of insecure
(non-HTTPS) registries.
client_timeout
Timeout in seconds for the Docker client. This is not a timeout for
the state, but for receiving a response from the API.
@ -212,6 +219,10 @@ def present(
``pillar_roots`` or an external Pillar source.
.. versionadded:: 2018.3.0
kwargs
Additional keyword arguments to pass to
:py:func:`docker.build <salt.modules.dockermod.build>`
"""
ret = {"name": name, "changes": {}, "result": False, "comment": ""}
@ -375,6 +386,9 @@ def absent(name=None, images=None, force=False):
specified either using ``repo:tag`` notation, or just the repo name (in
which case a tag of ``latest`` is assumed).
name
The name of the docker image.
images
Run this state on more than one image at a time. The following two
examples accomplish the same thing:
@ -401,7 +415,7 @@ def absent(name=None, images=None, force=False):
all the deletions in a single run, rather than executing the state
separately on each image (as it would in the first example).
force : False
force
Salt will fail to remove any images currently in use by a container.
Set this option to true to remove the image even if it is already
present.

View file

@ -2649,7 +2649,7 @@ class SaltKeyOptionParser(
default=".",
help=(
"Set the directory to save the generated keypair, only "
"works with \"gen_keys_dir\" option. Default: '%default'."
"works with \"--gen-keys\" option. Default: '%default'."
),
)
@ -2767,10 +2767,11 @@ class SaltKeyOptionParser(
def process_gen_keys_dir(self):
# Schedule __create_keys_dir() to run if there's a value for
# --create-keys-dir
self._mixin_after_parsed_funcs.append(
self.__create_keys_dir
) # pylint: disable=no-member
# --gen-keys-dir
if self.options.gen_keys:
self._mixin_after_parsed_funcs.append(
self.__create_keys_dir
) # pylint: disable=no-member
def __create_keys_dir(self):
if not os.path.isdir(self.config["gen_keys_dir"]):

View file

@ -44,7 +44,7 @@ class PublishModuleTest(ModuleCase, SaltReturnAssertsMixin):
self.assertTrue(name in ret)
self.assertEqual(ret["cheese"], "spam")
self.assertEqual(ret["__pub_arg"], [{"cheese": "spam"}])
self.assertEqual(ret["__pub_arg"], [{"__kwarg__": True, "cheese": "spam"}])
self.assertEqual(ret["__pub_id"], "minion")
self.assertEqual(ret["__pub_fun"], "test.kwarg")
@ -125,7 +125,7 @@ class PublishModuleTest(ModuleCase, SaltReturnAssertsMixin):
self.assertTrue(name in ret)
self.assertEqual(ret["cheese"], "spam")
self.assertEqual(ret["__pub_arg"], [{"cheese": "spam"}])
self.assertEqual(ret["__pub_arg"], [{"__kwarg__": True, "cheese": "spam"}])
self.assertEqual(ret["__pub_id"], "minion")
self.assertEqual(ret["__pub_fun"], "test.kwarg")

View file

@ -353,3 +353,38 @@ def test_onlyif_req(state, subtests):
assert ret.result is False
assert ret.changes
assert ret.comment == "Failure!"
def test_listen_requisite_not_exist(state, state_tree):
"""
Tests a simple state using the listen requisite
when the state id does not exist
"""
sls_contents = """
successful_changing_state:
cmd.run:
- name: echo "Successful Change"
non_changing_state:
test.succeed_without_changes
test_listening_change_state:
cmd.run:
- name: echo "Listening State"
- listen:
- cmd: successful_changing_state
test_listening_non_changing_state:
cmd.run:
- name: echo "Only run once"
- listen:
- test: non_changing_state_not_exist
"""
with pytest.helpers.temp_file("requisite.sls", sls_contents, state_tree):
ret = state.sls("requisite")
assert (
ret.raw[
"Listen_Error_|-listen_non_changing_state_not_exist_|-listen_test_|-Listen_Error"
]["comment"]
== "Referenced state test: non_changing_state_not_exist does not exist"
)

View file

@ -7,6 +7,37 @@ pytestmark = [
pytest.mark.core_test,
]
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
@pytest.fixture
def configure_loader_modules(minion_opts):
return {
state_mod: {
"__opts__": minion_opts,
"__salt__": {
"config.option": config.option,
"config.get": config.get,
"saltutil.is_running": saltutil.is_running,
"grains.get": grains.get,
"cmd.run": cmd.run,
},
},
config: {
"__opts__": minion_opts,
},
saltutil: {
"__opts__": minion_opts,
},
grains: {
"__opts__": minion_opts,
},
}
def test_requisites_mixed_require_prereq_use_1(state, state_tree):
"""
@ -401,3 +432,23 @@ def test_issue_30161_unless_and_onlyif_together(state, state_tree, tmp_path):
}
for slsid in _expected:
assert ret[slsid].comment == _expected[slsid]["comment"]
def test_requisites_mixed_illegal_req(state_tree):
"""
Call sls file containing several requisites.
When one of the requisites is illegal.
"""
sls_contents = """
A:
cmd.run:
- name: echo A
B:
cmd.run:
- name: echo B
- require:
- cmd: ["A"]
"""
with pytest.helpers.temp_file("requisite.sls", sls_contents, state_tree):
ret = state_mod.sls("requisite")
assert ret == ["Illegal requisite \"['A']\", please check your syntax.\n"]

View file

@ -7,10 +7,16 @@ import time
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__)
@ -22,6 +28,32 @@ pytestmark = [
]
@pytest.fixture
def configure_loader_modules(minion_opts):
return {
state_mod: {
"__opts__": minion_opts,
"__salt__": {
"config.option": config.option,
"config.get": config.get,
"saltutil.is_running": saltutil.is_running,
"grains.get": grains.get,
"cmd.run": cmd.run,
},
"__utils__": {"state.check_result": state_util.check_result},
},
config: {
"__opts__": minion_opts,
},
saltutil: {
"__opts__": minion_opts,
},
grains: {
"__opts__": minion_opts,
},
}
def _check_skip(grains):
if grains["os"] == "SUSE":
return True
@ -1032,3 +1064,20 @@ def test_state_sls_defaults(state, state_tree):
for state_return in ret:
assert state_return.result is True
assert "echo 1" in state_return.comment
def test_state_sls_mock_ret(state_tree):
"""
test state.sls when mock=True is passed
"""
sls_contents = """
echo1:
cmd.run:
- name: "echo 'This is a test!'"
"""
with pytest.helpers.temp_file("mock.sls", sls_contents, state_tree):
ret = state_mod.sls("mock", mock=True)
assert (
ret["cmd_|-echo1_|-echo 'This is a test!'_|-run"]["comment"]
== "Not called, mocked"
)

View file

@ -337,6 +337,13 @@ def test_keys_generation(salt_key_cli, tmp_path):
filename.chmod(0o700)
def test_gen_keys_dir_without_gen_keys(salt_key_cli, tmp_path):
gen_keys_path = tmp_path / "temp-gen-keys-path"
ret = salt_key_cli.run("--gen-keys-dir", str(gen_keys_path))
assert ret.returncode == 0
assert not gen_keys_path.exists()
def test_keys_generation_keysize_min(salt_key_cli, tmp_path):
ret = salt_key_cli.run(
"--gen-keys", "minibar", "--gen-keys-dir", str(tmp_path), "--keysize", "1024"

View file

@ -0,0 +1,669 @@
import logging
import pytest
import salt.minion
import salt.state
from salt.utils.odict import OrderedDict
from tests.support.mock import MagicMock, patch
log = logging.getLogger(__name__)
pytestmark = [
pytest.mark.core_test,
]
def test_compiler_render_template(minion_opts, tmp_path):
"""
Test Compiler.render_template
"""
minion = "poc-minion"
kwargs = {
"tag": f"salt/minion/{minion}/start",
"data": {
"id": minion,
"cmd": "_minion_event",
"pretag": None,
"data": f"Minion {minion} started at Thu Sep 14 07:31:04 2023",
"tag": f"salt/minion/{minion}/start",
"_stamp": "2023-09-14T13:31:05.000316",
},
}
reactor_file = tmp_path / "reactor.sls"
content = f"""
highstate_run:
local.state.apply:
- tgt: {minion}
- args:
- mods: test
"""
with salt.utils.files.fopen(reactor_file, "w") as fp:
fp.write(content)
mminion = salt.minion.MasterMinion(minion_opts)
comp = salt.state.Compiler(minion_opts, mminion.rend)
ret = comp.render_template(template=str(reactor_file), kwargs=kwargs)
assert ret["highstate_run"]["local"][0]["tgt"] == minion
assert ret["highstate_run"]["local"][1]["args"][0]["mods"] == "test"
def test_compiler_render_template_doesnotexist(minion_opts, tmp_path):
"""
Test Compiler.render_template when
the reactor file does not exist
"""
minion = "poc-minion"
kwargs = {
"tag": f"salt/minion/{minion}/start",
"data": {
"id": minion,
"cmd": "_minion_event",
"pretag": None,
"data": f"Minion {minion} started at Thu Sep 14 07:31:04 2023",
"tag": f"salt/minion/{minion}/start",
"_stamp": "2023-09-14T13:31:05.000316",
},
}
reactor_file = tmp_path / "reactor.sls"
mminion = salt.minion.MasterMinion(minion_opts)
comp = salt.state.Compiler(minion_opts, mminion.rend)
mock_pad = MagicMock(return_value=None)
patch_pad = patch.object(comp, "pad_funcs", mock_pad)
with patch_pad:
ret = comp.render_template(template=str(reactor_file), kwargs=kwargs)
assert ret == {}
mock_pad.assert_not_called()
def test_compiler_pad_funcs(minion_opts, tmp_path):
"""
Test Compiler.pad_funcs
"""
high = OrderedDict(
[
(
"highstate_run",
OrderedDict(
[
(
"local.state.apply",
[
OrderedDict([("tgt", "poc-minion")]),
OrderedDict(
[("args", [OrderedDict([("mods", "test")])])]
),
],
)
]
),
)
]
)
exp = OrderedDict(
[
(
"highstate_run",
OrderedDict(
[
(
"local",
[
OrderedDict([("tgt", "poc-minion")]),
OrderedDict(
[("args", [OrderedDict([("mods", "test")])])]
),
"state.apply",
],
)
]
),
)
]
)
mminion = salt.minion.MasterMinion(minion_opts)
comp = salt.state.Compiler(minion_opts, mminion.rend)
ret = comp.pad_funcs(high)
assert ret == exp
def test_compiler_pad_funcs_short_sls(minion_opts, tmp_path):
"""
Test Compiler.pad_funcs when using a shorter
sls with no extra arguments
"""
high = OrderedDict([("master_pub", "wheel.key.master_key_str")])
exp = OrderedDict([("master_pub", {"wheel": ["key.master_key_str"]})])
mminion = salt.minion.MasterMinion(minion_opts)
comp = salt.state.Compiler(minion_opts, mminion.rend)
ret = comp.pad_funcs(high)
assert ret == exp
@pytest.mark.parametrize(
"high,exp",
[
(
{
"master_pub": {
"wheel": ["key.master_key_str"],
"__sls__": "/srv/reactor/start.sls",
}
},
[],
),
(set(), ["High data is not a dictionary and is invalid"]),
(
{
1234: {
"wheel": ["key.master_key_str"],
"__sls__": "/srv/reactor/start.sls",
}
},
[
"ID '1234' in SLS '/srv/reactor/start.sls' is not formed as a string, but is a int. It may need to be quoted"
],
),
(
{
b"test": {
"wheel": ["key.master_key_str"],
"__sls__": "/srv/reactor/start.sls",
}
},
[
"ID 'b'test'' in SLS '/srv/reactor/start.sls' is not formed as a string, but is a bytes. It may need to be quoted"
],
),
(
{
True: {
"wheel": ["key.master_key_str"],
"__sls__": "/srv/reactor/start.sls",
}
},
[
"ID 'True' in SLS '/srv/reactor/start.sls' is not formed as a string, but is a bool. It may need to be quoted"
],
),
(
{"master_pub": ["wheel", "key.master_key_str"]},
[
"The type master_pub in ['wheel', 'key.master_key_str'] is not formatted as a dictionary"
],
),
(
{
"master_pub": {
"wheel": {"key.master_key_str"},
"__sls__": "/srv/reactor/start.sls",
}
},
[
"State 'master_pub' in SLS '/srv/reactor/start.sls' is not formed as a list"
],
),
(
{
"master_pub": {
"wheel": ["key. master_key_str"],
"__sls__": "/srv/reactor/start.sls",
}
},
[
'The function "key. master_key_str" in state "master_pub" in SLS "/srv/reactor/start.sls" has whitespace, a function with whitespace is not supported, perhaps this is an argument that is missing a ":"'
],
),
(
{
"master_pub": {
"wheel": ["key.master_key_str "],
"__sls__": "/srv/reactor/start.sls",
}
},
[],
),
],
)
def test_compiler_verify_high_short_sls(minion_opts, tmp_path, high, exp):
"""
Test Compiler.verify_high when using
a shorter sls with know extra arguments
"""
mminion = salt.minion.MasterMinion(minion_opts)
comp = salt.state.Compiler(minion_opts, mminion.rend)
ret = comp.verify_high(high)
# empty is successful. Means we have no errors
assert ret == exp
@pytest.mark.parametrize(
"high,exp",
[
(
{
"add_test_1": OrderedDict(
[
(
"local",
[
OrderedDict([("tgt", "poc-minion")]),
OrderedDict(
[
(
"args",
[
OrderedDict(
[("cmd", "touch /tmp/test1")]
)
],
)
]
),
"cmd.run",
],
),
("__sls__", "/srv/reactor/start.sls"),
]
),
"add_test_2": OrderedDict(
[
(
"local",
[
OrderedDict([("tgt", "poc-minion")]),
OrderedDict(
[
(
"args",
[
OrderedDict(
[("cmd", "touch /tmp/test2")]
)
],
)
]
),
OrderedDict(
[
(
"require",
[OrderedDict([("local", "add_test_1")])],
)
]
),
"cmd.run",
],
),
("__sls__", "/srv/reactor/start.sls"),
]
),
},
[],
),
(
{
"add_test_1": OrderedDict(
[
(
"local",
[
OrderedDict([("tgt", "poc-minion")]),
OrderedDict(
[
(
"args",
[
OrderedDict(
[("cmd", "touch /tmp/test1")]
)
],
)
]
),
"cmd.run",
],
),
("__sls__", "/srv/reactor/start.sls"),
]
),
"add_test_2": OrderedDict(
[
(
"local",
[
OrderedDict([("tgt", "poc-minion")]),
OrderedDict(
[
(
"args",
[
OrderedDict(
[("cmd", "touch /tmp/test2")]
)
],
)
]
),
OrderedDict([("require", {"local": "add_test_1"})]),
"cmd.run",
],
),
("__sls__", "/srv/reactor/start.sls"),
]
),
},
[
"The require statement in state 'add_test_2' in SLS '/srv/reactor/start.sls' needs to be formed as a list"
],
),
(
{
"add_test_1": OrderedDict(
[
(
"local",
[
OrderedDict([("tgt", "poc-minion")]),
OrderedDict(
[
(
"args",
[
OrderedDict(
[("cmd", "touch /tmp/test1")]
)
],
)
]
),
"cmd.run",
],
),
("__sls__", "/srv/reactor/start.sls"),
]
),
"add_test_2": OrderedDict(
[
(
"local.cmd.run",
[
OrderedDict([("tgt", "poc-minion")]),
OrderedDict(
[
(
"args",
[
OrderedDict(
[("cmd", "touch /tmp/test2")]
)
],
)
]
),
OrderedDict([("require", {"local": "add_test_1"})]),
"cmd.run",
],
),
("__sls__", "/srv/reactor/start.sls"),
]
),
},
[
"The require statement in state 'add_test_2' in SLS '/srv/reactor/start.sls' needs to be formed as a list",
"Too many functions declared in state 'local.cmd.run' in SLS '/srv/reactor/start.sls'",
],
),
(
{
"add_test_1": OrderedDict(
[
(
"local",
[
OrderedDict([("tgt", "poc-minion")]),
OrderedDict(
[("args", ([("cmd", "touch /tmp/test1")]))]
),
"cmd.run",
],
),
("__sls__", "/srv/reactor/start.sls"),
]
),
"add_test_2": OrderedDict(
[
(
"local",
[
OrderedDict([("tgt", "poc-minion")]),
OrderedDict(
[
(
"args",
[
OrderedDict(
[("cmd", "touch /tmp/test2")]
)
],
)
]
),
OrderedDict([("require", ([("local", "add_test_1")]))]),
"cmd.run",
],
),
("__sls__", "/srv/reactor/start.sls"),
]
),
},
[
"Requisite declaration ('local', 'add_test_1') in SLS /srv/reactor/start.sls is not formed as a single key dictionary"
],
),
(
{
"add_test_1": OrderedDict(
[
(
"local",
[
OrderedDict([("tgt", "poc-minion")]),
OrderedDict(
[
(
"args",
[
OrderedDict(
[("cmd", "touch /tmp/test1")]
)
],
)
]
),
"cmd.run",
],
),
("__sls__", "/srv/reactor/start.sls"),
]
),
"add_test_2": OrderedDict(
[
(
"local",
[
OrderedDict([("tgt", "poc-minion")]),
OrderedDict(
[
(
"args",
[
OrderedDict(
[("cmd", "touch /tmp/test2")]
)
],
)
]
),
OrderedDict(
[
(
"require",
[
OrderedDict(
[("local", (["add_test_1"]))]
)
],
)
]
),
"cmd.run",
],
),
("__sls__", "/srv/reactor/start.sls"),
]
),
},
["Illegal requisite \"['add_test_1']\", is SLS /srv/reactor/start.sls\n"],
),
(
{
"add_test_1": OrderedDict(
[
(
"local",
[
OrderedDict([("tgt", "poc-minion")]),
OrderedDict(
[
(
"args",
[
OrderedDict(
[("cmd", "touch /tmp/test1")]
)
],
)
]
),
"cmd.run",
],
),
("__sls__", "/srv/reactor/start.sls"),
]
),
"add_test_2": OrderedDict(
[
(
"local",
[
OrderedDict([("tgt", "poc-minion")]),
OrderedDict(
[
(
"args",
[
OrderedDict(
[("cmd", "touch /tmp/test2")]
)
],
)
]
),
OrderedDict(
[
(
"require",
[OrderedDict([("local", "add_test_2")])],
)
]
),
"cmd.run",
],
),
("__sls__", "/srv/reactor/start.sls"),
]
),
},
[
'A recursive requisite was found, SLS "/srv/reactor/start.sls" ID "add_test_2" ID "add_test_2"'
],
),
(
{
"add_test_1": OrderedDict(
[
(
"local",
[
OrderedDict([("tgt", "poc-minion")]),
OrderedDict(
[
(
"args",
[
OrderedDict(
[("cmd", "touch /tmp/test1")]
)
],
)
]
),
"cmd.run",
],
),
("__sls__", "/srv/reactor/start.sls"),
]
),
"add_test_2": OrderedDict(
[
(
"local",
[
OrderedDict([("tgt", "poc-minion")]),
OrderedDict(
[
(
"args",
[
OrderedDict(
[("cmd", "touch /tmp/test2")]
)
],
)
]
),
OrderedDict(
[
(
"require",
[OrderedDict([("local", "add_test_1")])],
)
]
),
"cmd.run",
],
),
("__sls__", "/srv/reactor/start.sls"),
]
),
},
[],
),
],
)
def test_compiler_verify_high_sls_requisites(minion_opts, tmp_path, high, exp):
"""
Test Compiler.verify_high when using
a sls with requisites
"""
mminion = salt.minion.MasterMinion(minion_opts)
comp = salt.state.Compiler(minion_opts, mminion.rend)
ret = comp.verify_high(high)
# empty is successful. Means we have no errors
assert ret == exp

View file

@ -38,6 +38,16 @@ def test_format_log_non_ascii_character():
salt.state.format_log(ret)
def test_format_log_list(caplog):
"""
Test running format_log when ret is not a dictionary
"""
ret = ["test1", "test2"]
salt.state.format_log(ret)
assert "INFO" in caplog.text
assert f"{ret}" in caplog.text
def test_render_error_on_invalid_requisite(minion_opts):
"""
Test that the state compiler correctly deliver a rendering

View file

@ -8,6 +8,7 @@ import tornado.gen
import tornado.testing
import salt.minion
import salt.modules.test as test_mod
import salt.syspaths
import salt.utils.crypt
import salt.utils.event as event
@ -1109,3 +1110,19 @@ async def test_syndic_async_req_channel(syndic_opts):
syndic.pub_channel = MagicMock()
syndic.tune_in_no_block()
assert isinstance(syndic.async_req_channel, salt.channel.client.AsyncReqChannel)
@pytest.mark.slow_test
def test_load_args_and_kwargs(minion_opts):
"""
Ensure load_args_and_kwargs performs correctly
"""
_args = [{"max": 40, "__kwarg__": True}]
ret = salt.minion.load_args_and_kwargs(test_mod.rand_sleep, _args)
assert ret == ([], {"max": 40})
assert all([True if "__kwarg__" in item else False for item in _args])
# Test invalid arguments
_args = [{"max_sleep": 40, "__kwarg__": True}]
with pytest.raises(salt.exceptions.SaltInvocationError):
ret = salt.minion.load_args_and_kwargs(test_mod.rand_sleep, _args)