Skip to content

Merge forward 3008.x into master#69491

Merged
dwoz merged 129 commits into
saltstack:masterfrom
dwoz:merge/3008.x/master-26-06-18
Jun 19, 2026
Merged

Merge forward 3008.x into master#69491
dwoz merged 129 commits into
saltstack:masterfrom
dwoz:merge/3008.x/master-26-06-18

Conversation

@dwoz

@dwoz dwoz commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

No description provided.

Daniel A. Wozniak and others added 30 commits May 27, 2026 17:02
After the requirements .txt -> .in rename, MANIFEST.in still only
included requirements/*.txt, so the PyPI sdist no longer shipped the
files setup.py reads to populate install_requires. The build backend
silently returned an empty list, and `pip install salt==3008.0`
installed salt with zero dependencies.

Add *.in and *.lock to the recursive-include so the dependency manifests
(and the locked variants used when USE_STATIC_REQUIREMENTS=1) end up in
the sdist again.
doc/conf.py hard-coded "version_match": "master", so every published
build (including the 3008.0 release at /en/latest/) emitted
DOCUMENTATION_OPTIONS.theme_switcher_version_match = 'master' and the
dropdown always highlighted "Master (Dev)" as the current version.

Make version_match dynamic so it matches a versions.json entry by
BUILD_TYPE:
  - master / next       -> "master"
  - latest              -> "latest"
  - previous / other    -> release.split(".", 1)[0]  (e.g. "3006")

Also fix the dropdown URLs in doc/_static/versions.json. The 3006.24
entry pointed at /en/3006.24/ which returns 404; the served URL pattern
is /en/<major>/ (e.g. /en/3006/, /en/3008/). Rewrite the entries to use
major-only paths and add a 3008 entry, since the latest release at
docs.saltproject.io is now 3008.0.

Note: doc/conf.py points every build at the absolute json_url
https://docs.saltproject.io/en/latest/_static/versions.json, so the
served versions.json is whichever branch publishes /en/latest/. Updating
versions.json here is harmless on 3006.x but only takes effect site-wide
when the same change reaches the branch that builds /en/latest/.

Verified locally with the CI invocation under a python3.14 venv built
from requirements/static/ci/py3.14/docs.lock:
  cd doc && make clean && make html SPHINXOPTS='-W -j auto --keep-going --color'
  cd doc && make clean && BUILD_TYPE=latest make html SPHINXOPTS='-W -j auto --keep-going --color'
Rendered contents.html emits:
  theme_switcher_version_match = 'master'   (default)
  theme_switcher_version_match = 'latest'   (BUILD_TYPE=latest)
`test_master_minion_start` installs from the source directory, so pip
builds a wheel in place and `setup.py` reads requirements files
directly off disk. That path never consults MANIFEST.in, so it can't
catch a regression where the published sdist is missing the files
`setup.py` needs to populate install_requires -- which is precisely
how saltstack#69244 reached PyPI undetected.

Add a sibling fixture that runs `python -m build --sdist` first, then
pip-installs the resulting tarball into a fresh venv. The existing
master/minion startup + salt-call test.ping assertions then catch a
dependency-less install because salt-master crashes on tornado import.

Verified against the unpatched 3008.x: the new fixture's pip install
produces a salt with no dependencies (`import tornado` raises
ModuleNotFoundError), reproducing the user-reported failure.
pkg.list_patches in yumpkg.py  parses tdnf output on Photon OS
Ship requirements/*.in and *.lock in sdist
Fix docs version switcher selecting the wrong active version
Add sdist-roundtrip sibling test for pip install salt (saltstack#69244)
After the requirements .txt -> .in rename, MANIFEST.in still only
included requirements/*.txt, so the PyPI sdist no longer shipped the
files setup.py reads to populate install_requires. The build backend
silently returned an empty list, and `pip install salt` from a sdist
built off this branch would install salt with zero dependencies.

Add *.in and *.lock to the recursive-include so the dependency manifests
(and the locked variants used when USE_STATIC_REQUIREMENTS=1) end up in
the sdist again. Backport of the 3008.x fix; changelog lives on the
forward branch.
After the requirements .txt -> .in rename, MANIFEST.in still only
included requirements/*.txt, so the PyPI sdist no longer shipped the
files setup.py reads to populate install_requires. The build backend
silently returned an empty list, and `pip install salt` from a sdist
built off this branch would install salt with zero dependencies.

Add *.in and *.lock to the recursive-include so the dependency manifests
(and the locked variants used when USE_STATIC_REQUIREMENTS=1) end up in
the sdist again. Backport of the 3008.x fix; changelog lives on the
forward branch.
Revert requirement source files to .txt to fix Dependabot sync
Revert requirement source files to .txt to fix Dependabot sync
After the requirements .txt -> .in rename, MANIFEST.in still only
included requirements/*.txt, so the PyPI sdist no longer shipped the
files setup.py reads to populate install_requires. The build backend
silently returned an empty list, and `pip install salt` from a sdist
built off this branch would install salt with zero dependencies.

Add *.in and *.lock to the recursive-include so the dependency manifests
(and the locked variants used when USE_STATIC_REQUIREMENTS=1) end up in
the sdist again. Backport of the 3008.x fix; changelog lives on the
forward branch.

# Conflicts:
#	requirements/static/ci/py3.10/cloud.lock
#	requirements/static/ci/py3.10/darwin.lock
#	requirements/static/ci/py3.10/freebsd.lock
#	requirements/static/ci/py3.10/lint.lock
#	requirements/static/ci/py3.10/linux.lock
#	requirements/static/ci/py3.10/windows.lock
#	requirements/static/ci/py3.11/cloud.lock
#	requirements/static/ci/py3.11/darwin.lock
#	requirements/static/ci/py3.11/freebsd.lock
#	requirements/static/ci/py3.11/lint.lock
#	requirements/static/ci/py3.11/linux.lock
#	requirements/static/ci/py3.11/windows.lock
#	requirements/static/ci/py3.12/cloud.lock
#	requirements/static/ci/py3.12/darwin.lock
#	requirements/static/ci/py3.12/freebsd.lock
#	requirements/static/ci/py3.12/lint.lock
#	requirements/static/ci/py3.12/linux.lock
#	requirements/static/ci/py3.12/windows.lock
#	requirements/static/ci/py3.13/cloud.lock
#	requirements/static/ci/py3.13/darwin.lock
#	requirements/static/ci/py3.13/freebsd.lock
#	requirements/static/ci/py3.13/lint.lock
#	requirements/static/ci/py3.13/linux.lock
#	requirements/static/ci/py3.13/windows.lock
#	requirements/static/ci/py3.14/cloud.lock
#	requirements/static/ci/py3.14/darwin.lock
#	requirements/static/ci/py3.14/freebsd.lock
#	requirements/static/ci/py3.14/lint.lock
#	requirements/static/ci/py3.14/linux.lock
#	requirements/static/ci/py3.14/windows.lock
#	requirements/static/ci/py3.9/cloud.lock
#	requirements/static/ci/py3.9/darwin.lock
#	requirements/static/ci/py3.9/freebsd.lock
#	requirements/static/ci/py3.9/lint.lock
#	requirements/static/ci/py3.9/linux.lock
#	requirements/static/ci/py3.9/windows.lock
``dynamic_context = test_function`` forces coverage.py off the
sys.monitoring (sysmon) measurement core — sysmon does not yet
implement dynamic contexts.  On Python 3.14 sysmon is the default
and is dramatically faster than the PyTracer / CTracer fallbacks.

With the coverage pin we currently carry (7.3.1), no CTracer wheel
ships for 3.14 either, so dynamic_context drops the test suite all
the way down to the pure-Python PyTracer.  That combination — the
slow PyTracer plus relenv's runtime wrappers around sysconfig —
makes every forked subprocess's ``cov.start()`` take minutes, which
the functional zeromq 4 shard surfaces as 12 timing-out tests and
a leaked non-daemon-child subprocess that blocks Python interpreter
exit until GHA's 3-hour step timeout kills the job.

Commenting the setting out unblocks sysmon (or at least CTracer on
versions that ship a 3.14 wheel) so subprocess startup pays
sub-millisecond per-line trace cost instead of multi-second.  The
inline comment in ``.coveragerc`` captures the symptoms and the
re-enable condition.

Refs:
  coveragepy/coveragepy#2082
  https://coverage.readthedocs.io/en/latest/contexts.html
  https://coverage.readthedocs.io/en/latest/faq.html
Salt is now running on a Python 3.14 onedir and the prior coverage
pin (7.3.1) ships no CTracer wheel for 3.14 — coverage falls back to
the pure-Python PyTracer.  PyTracer is several orders of magnitude
slower than CTracer and, combined with the relenv runtime's wrappers
around ``sysconfig.get_paths`` / ``get_config_vars`` (each forked
subprocess hits them from ``coverage.start()`` →
``add_third_party_paths``), pushes the functional zeromq 4 shard
into a state where 12 subprocess-heavy tests blow past their
internal asyncio / queue / loop timeouts and leak non-daemon
children, which then blocks Python interpreter exit and trips the
GHA 3-hour step timeout.

7.14.0 is the current latest release where the 3.14 CTracer wheel is
mature.  Skip the 7.11.1–7.11.3 window — those have a known 2x perf
regression on Python 3.14 (coveragepy issue saltstack#2082).

The companion ``.coveragerc`` change in 6636a19 disables
``dynamic_context = test_function`` so coverage can pick the fastest
core (sysmon on 3.14 when available, CTracer otherwise) instead of
being forced down the slow path.

Refs:
  coveragepy/coveragepy#2082
  https://coverage.readthedocs.io/en/latest/changes.html
Previous commit put the ``a1_coverage.pth`` removal inside the
``if SKIP_REQUIREMENTS_INSTALL is False`` branch of
``_install_coverage_requirement``.  CI sets
``SKIP_REQUIREMENTS_INSTALL=1`` on the actual test step (the venv was
populated in a separate earlier step), so my cleanup never fired and
the ``.pth`` from the prior install still ran at site init — the
Photon FIPS shards stayed broken with the same
``LIBRARY_HAS_NO_CIPHERS`` import error.

Move the ``.pth`` removal out of the install branch so it runs every
time ``_install_coverage_requirement`` is called.  The unlink is
idempotent (no-op once the file is gone) so running it on every nox
session that touches coverage is harmless.

Confirmed in the failing job 26262301538 logs that the install branch
was skipped::

    nox > Skipping Python Requirements because SKIP_REQUIREMENTS_INSTALL
          was found in the environ
    ImportError while loading conftest '/__w/salt/salt/tests/conftest.py'
    ...
    ssl.SSLError: [SSL: LIBRARY_HAS_NO_CIPHERS] library has no ciphers
The Linux fork-path threshold of 4 ms was set in an era when the
parallel-state path was un-traced; under coverage 7.14 + sysmon on
Python 3.14 the forked child's loader lookup + ``file.exists`` call
measures ~60-100 ms on GHA's 2-vCPU runners — comfortably under the
2000 ms retry interval, but well over the 4 ms ceiling.

This test asserts two things:

  - ``state_return.result is True``                  (operation succeeded)
  - ``state_return.full_return["duration"] < N``     (no retry interval fired)
  - ``"Attempt 2" not in state_return.comment``      (no retry attempt)

The middle assert is a sanity check against the 2000 ms retry
interval, not a microbenchmark of the fork path.  Bump the threshold
to a value that still catches a real retry firing while tolerating
realistic GHA-runner-under-coverage latency:

  - fork (Linux):       4 ms   ->   500 ms
  - spawn:             30 ms   ->  1500 ms
  - spawn + darwin:    +15 ms  ->  +500 ms

The Linux fork failure pattern in CI run 26271348038 was the
universal cause of ``functional zeromq 1`` shard failures across
every distro (Debian 11/12/13, Amazon, Fedora, Rocky, Ubuntu,
Photon 4/5 + FIPS).
leifliddy and others added 11 commits June 17, 2026 03:27
salt:///foo (RFC 3986 empty authority + absolute path) was historically
accepted as an equivalent spelling of salt://foo, but on 3008.x
cp.get_file salt:///path/to/file fails because the surplus leading
slash propagates through parse() to the master fileserver, which
rejects absolute paths in find_file (CVE-2024-22231 /
CVE-2024-22232 hardening). On older releases the leading slash was
laundered away by the file:/// round-trip in salt.utils.url.create();
once that round-trip was removed for Python 3.13 compatibility
(4cdd334, ce02842) the bug became user-visible end-to-end.

Strip leading slashes from the parsed path so both URL spellings
behave identically. The fileserver always interprets salt:// paths
as relative to a file_roots entry, so a leading / was never
meaningful.

Fixes saltstack#69472
…t-3slash-url

Accept salt:/// (3-slash) URLs in salt.utils.url.parse
`pillar.get`, `pillar.items`, `pillar.item`, and `pillar.data` accept an
`unmask` kwarg added in 3008.0. This applies the same parameter to
`pillar.ls`, `pillar.raw`, `pillar.ext`, `pillar.keys`, and
`pillar.obfuscate` so callers have a uniform API across the module.

Default semantics are preserved:

  * `ls`, `obfuscate` forward `unmask` to `items` (whose default is
    auto-detect via the `mask_pillar` ContextVar).
  * `ext` mirrors `items`: auto-detect when `unmask=None`.
  * `raw` preserves its historical always-unmask default; setting
    `unmask=False` now lets callers obtain masked values.
  * `keys` accepts `unmask` for API uniformity; nested pillar keys are
    never masked, so the parameter is a no-op for this function.

Refs saltstack#69453
…ack#69228)

When the master returns a bare-string error payload such as "bad load",
"Some exception handling minion payload", or "Server-side exception
handling payload" for a pillar request, the minion's
AsyncReqChannel.crypted_transfer_decode_dictentry could crash with
"TypeError: string indices must be integers" at ret["key"]. The
pre-2efc32ea883 guard ("key" not in ret) silently passed for strings
because Python interprets it as a substring check, then the post-reauth
retry returned the same string and the dict-style indexing blew up.

Master already carries the fix from commit 2efc32e ("Unblock raft
heartbeats and harden pillar decode against cluster_retry"), which adds
layered isinstance(ret, dict) guards and raises a clean
AuthenticationError when the master keeps returning a non-dict. That
fix covers this scenario as a side effect of the cluster_retry hardening
but had no dedicated regression test.

Add a parametrized test that mocks the transport to return each of the
three observed bare-string error payloads and asserts the channel
raises salt.crypt.AuthenticationError rather than crashing with
TypeError. Verified the test fails on the pre-2efc32ea883 code path
with the exact stacktrace reported in the issue, and passes on current
master.

Fixes saltstack#69228
Two bugs introduced in saltstack#68039 combined to delete every auth token
from the default localfs cache backend within one master
``loop_interval`` (default 60s):

1. ``Cache.clean_expired``'s fallback (for drivers without a native
   ``clean_expired``) compared the driver's ``updated()`` value --
   for localfs the file mtime, an epoch in the past -- with
   ``time.time()`` and flushed every key whose mtime had passed.
   That is true for every file on disk, so the entire bank was
   wiped on every sweep.

2. ``LoadAuth.mk_token`` passed ``expires=tdata["expire"]`` to
   ``Cache.store``, where ``tdata["expire"]`` is the absolute epoch
   ``time.time() + token_expire``. ``Cache.store``'s ``expires``
   parameter is documented as a relative duration in seconds, so the
   envelope ``_expires`` was being written as ``now + (now +
   token_expire)`` -- roughly year 4090.

Replace the fallback with one that consults the ``_expires``
envelope ``Cache.store`` writes (entries with no envelope have no
cache-level expiry and are left alone), and pass the relative
``token_expire`` duration from ``mk_token``. Adds regression
coverage at both the cache-driver and ``LoadAuth`` layers.

Fixes saltstack#69307

@sujitdb sujitdb left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@dwoz dwoz merged commit 445d03e into saltstack:master Jun 19, 2026
488 of 493 checks passed
- Improved documentation for the `runas` and `password` parameters in `cmd.run`, `cmd.script`, and all `salt.modules.cmdmod` execution functions on Windows. The docs now accurately describe when a password is required: only when the salt-minion is **not** running as SYSTEM or as an elevated Administrator. Removed the inaccurate claim that the target user account must be in the Administrators group. Also changed `cmd.script` to log a warning instead of hard-failing when `runas` is used without a password on Windows, since a password is not always required. [#57951](https://github.com/saltstack/salt/issues/57951)
- Fixed `SSL: DECRYPTION_FAILED_OR_BAD_RECORD_MAC` errors in the VMware cloud driver by reconnecting when a cached vCenter service instance is found to be stale or corrupted (for example when inherited across a fork by salt-cloud's parallel provider queries). [#61983](https://github.com/saltstack/salt/issues/61983)
- Fixed event signature verification failing under ``minion_sign_messages``. The minion was signing the return load before ``salt.channel.client.AsyncReqChannel._package_load`` attached transport metadata (``nonce``, ``ts``, ``tok``, ``id``), so the bytes the master re-serialized to verify did not match what was signed and every signed return was dropped. Signing is now performed inside ``_package_load`` after the metadata is attached, against the same bytes the master verifies. [#68181](https://github.com/saltstack/salt/issues/68181)
- Fixed two distinct bugs in the `salt.engines.redis_sentinel` engine that [#69031](https://github.com/saltstack/salt/issues/69031)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We had the feedback about this showing multiple commits together

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

test:full Run the full test suite

Projects

None yet

Development

Successfully merging this pull request may close these issues.

10 participants