Skip to content

Merge forward 3007.x into 3008.x - #69908

Open
dwoz wants to merge 133 commits into
saltstack:3008.xfrom
dwoz:merge/3007.x/3008.x-07-29-26
Open

Merge forward 3007.x into 3008.x#69908
dwoz wants to merge 133 commits into
saltstack:3008.xfrom
dwoz:merge/3007.x/3008.x-07-29-26

Conversation

@dwoz

@dwoz dwoz commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

No description provided.

saltbot-open and others added 30 commits July 1, 2026 07:01
When a prereq chain is set up such that one state prereq's another
state that itself prereq's a third state (e.g. state1 --prereq-->
state2 --prereq--> state3), the intermediate state's own prereq
check node was created without its own prereq requirements. This
allowed the intermediate state to be evaluated in test mode
independently of the tail of the chain, so any state that always
proposes changes in test mode (e.g. test.succeed_with_changes,
module.run) caused the head of the chain to run even when the tail
produced no changes.

When adding a new prereq requisite (_add_prereq) from a chunk that
already has its own prereq check node (because another state prereq's
it), also register a PREREQ edge from the requisite's prereq check
node into the chunk's own prereq check node. This ensures the
intermediate prereq check waits for the tail's prereq check outcome
before deciding whether to run in test mode.

Also confirms the pre-existing regression test saltstack#68438 now passes on
Windows and Linux.

Fixes saltstack#68438
Restore the ``Test Salt / Rocky Linux 9 unit zeromq 4`` CI job to green
after the 3006.x→3007.x→3008.x merge-forward chain pulled 3006.x-only
regression tests into 3007.x whose expectations don't match the 3007.x
runtime API surface.

- ``test_verify_master_accepts_cached_key_with_whitespace_drift``,
  ``test_verify_master_caches_clean_key_on_first_contact``,
  ``test_authenticate_caps_retry_loop_with_auth_retries_69442``,
  ``test_authenticate_default_does_not_cap_retry_loop_69442``: switch
  from the removed 3006.x ``crypt.gen_keys(pki_dir, name, keysize)``
  signature to the 3007.x ``crypt.write_keys(...)`` equivalent, and
  add ``keys.cache_driver`` to opts so ``AsyncAuth.__singleton_init__``
  can construct the keystore cache.

- ``test_gen_signature_signs_clean_key`` and
  ``test_gen_signature_signs_clean_key_trailing_newline``: skip on
  3007.x. The module-level ``salt.crypt.gen_signature`` was removed by
  the master-pki cache refactor; the replacement
  ``MasterKeys.gen_signature`` signs ``pub.public_bytes()`` from a key
  object rather than the raw file content, so the saltstack#68930 whitespace-
  drift bug the tests were written against is not reachable on 3007.x.

- ``test_maintenance_duration``: add ``eauth_tokens.cache_driver``,
  ``eauth_tokens.cluster_id``, and ``cluster_id`` to the test's opts
  dict. ``Maintenance._post_fork_init`` now constructs a long-lived
  ``LoadAuth`` (as part of the memory-leak fix that caches it across
  loop iterations); ``LoadAuth.__init__`` reads those keys.

- ``test_minion_manager_stop_unblocks_resolve_dns_69466``: assert on
  either ``io_loop.create_task`` or ``io_loop.add_callback`` being
  called once. 3007.x refactored ``MinionManager.stop()`` to use
  ``create_task`` instead of ``add_callback``; the 3006.x-origin test
  hard-coded the older form.

- ``test_event_unpack_with_SaltDeserializationError``: assert on the
  new debug-level "skipping malformed event (deserialization error)"
  message that the memory-leak hardening emits from
  ``SaltEvent._get_event`` instead of the pre-hardening
  ``log.error("Unable to deserialize received event")`` call the test
  originally targeted. The hardening intentionally demotes the log
  level so a single bad IPC frame cannot spam the operator log.
The test used a fixed time.sleep(2) after spawning the salt CLI before
sending SIGINT. On slow CI hosts (observed on Photon OS 5 Arm64, both
tcp(fips) and zeromq(fips) integration lanes) the CLI had not yet
published its job when the signal arrived. Its scripts._handle_signals
path then took the AttributeError/KeyError branch (no pub_data), emitted
only "Exiting gracefully on Ctrl-c", and skipped the "This job's jid is"
message the test asserts on.

Wait on the master's salt/job/*/new event via event_listener instead.
That guarantees pub_data is populated in the CLI process before we
interrupt it, so the jid-bearing signal-handler branch always runs.

Fixes flakiness in
tests/pytests/integration/cli/test_salt.py::test_interrupt_on_long_running_job
seen at:
https://github.com/saltstack/salt/actions/runs/28505613358/job/84502049475
https://github.com/saltstack/salt/actions/runs/28505613358/job/84502049337
…prereq-chain-dag

Fix prereq chain regression in nested prereq DAG setup (saltstack#68438)
Two bugs kept the Sync .lock files job from running for 3007.x PRs, leaving lock
files stale:

- on.pull_request.branches omitted 3007.x (it only listed master and 3006.x), so
  the workflow never triggered for PRs targeting 3007.x. Add all four release
  branches.
- The actor guard only matched 'dependabot', so it skipped whenever the
  salt-pr-bot rebase bot re-pushed a branch. Also fire for salt-pr-bot.
Two bugs kept the Sync .lock files job from running for 3006.x PRs, leaving lock
files stale:

- on.pull_request.branches omitted 3006.x (it only listed master and 3006.x), so
  the workflow never triggered for PRs targeting 3006.x. Add all four release
  branches.
- The actor guard only matched 'dependabot', so it skipped whenever the
  salt-pr-bot rebase bot re-pushed a branch. Also fire for salt-pr-bot.
…c-fix-3006.x

Fix dependabot lock-sync workflow triggering on 3006.x
…c-fix-3007.x

Fix dependabot lock-sync workflow triggering on 3007.x
state.orchestrate overwrites __opts__["user"] with __user__ (the
publishing user, salt.utils.user.get_specific_user(), which returns
"sudo_<login>" when salt-run was launched under sudo). The post-saltstack#67716
privilege-drop path in saltutil.runner/saltutil.wheel reads that value
as the runas target and asks chugid to switch to it, which then raises
KeyError from pwd.getpwnam wrapped in CommandExecutionError:

    Failed to run 'cache.grains' as user 'sudo_alice':
    KeyError: "getpwnam(): name not found: 'sudo_alice'"

Validate the candidate against the passwd database in
_master_user_runas and skip the privilege drop when it does not
resolve to a real account, falling back to historical in-process
behavior.

Fixes saltstack#69600
SerializerExtension.load_yaml only guarded against a YAMLError whose
problem_mark is absent. PyYAML's libyaml (C) loader populates
problem_mark but leaves its buffer as None, so load_yaml took the else
branch and passed buffer=None into salt.utils.stringutils.get_context(),
which crashed with "AttributeError: 'NoneType' object has no attribute
'splitlines'" instead of raising the intended TemplateRuntimeError.

Fall back to the stringified exception when the buffer is missing, and
add a regression test.

Fixes saltstack#69533
…ltstack#69455)

The unmaintained `linode-python` 1.1.1 package targets the retired
Linode API v3 and uses `is not 0` / `is 1` against literals, which
Python 3.12+ emits as `SyntaxWarning` from `linode/api.py` lines
293, 348, and 356. On RHEL/Rocky/Oracle Linux 9.x, the salt-common
onedir's post-install scriptlet imports the onedir's Python which
in turn imports `linode-python` and the warnings surface during
every package install/upgrade.

Drop `linode-python` from `requirements/static/pkg/linux.txt` and
its dependent CI/pkg linux lockfiles so it is no longer installed
into the salt-common onedir. `salt.cloud.clouds.linode` already
uses the Linode APIv4 over HTTP/JSON (no `linode-python` import),
so the package is purely vestigial.

This backports saltstack#69339 (3006.x) and mirrors saltstack#68871 (master/3008.x)
to 3007.x.

Fixes saltstack#69455
The Fedora 40 skips on test_peer_communication and test_grains_remove_add
were added in 5a85699 (2024-05-17) as a temporary workaround
referencing saltstack#66539 and saltstack#66540 with no root cause captured. Fedora 40
reached end-of-life on 2025-05-13 and the tests now pass without the
workaround on 3007.x and master, so the skips can be dropped.

Fixes saltstack#66540
Fixes saltstack#66539
* fix timeout for salt-api

* Add changelog and regression test for salt-api timeout fix

Addresses @twangboy's CHANGES_REQUESTED on PR saltstack#62188.

- changelog/62187.fixed.md describes the salt-api hang fix.
- test_mk_token_missing_password_returns_empty pins the missing-password
  /username path: mk_token must return {} instead of raising
  SaltInvocationError, which previously escaped through the master
  clear-payload handler and hung salt-api workers for ~3 minutes per
  bad request.

Co-authored-by: carrysauce <carrysauce@users.noreply.github.com>

* Apply reviewer suggestions: separate try/except for format_call, rename changelog to PR number

- Give format_call its own try/except block catching SaltInvocationError
  with a descriptive debug message, as requested by @twangboy
- Rename changelog/62187.fixed.md to changelog/62188.fixed.md (PR number,
  not issue number)

---------

Co-authored-by: Alex Donec <alex.donec@pinely.com>
Co-authored-by: Daniel A. Wozniak <dwozniak@broadcom.com>
Co-authored-by: carrysauce <carrysauce@users.noreply.github.com>
Co-authored-by: Daniel A. Wozniak <daniel.wozniak@broadcom.com>
* Fix onchanges requisites hard-failing when their target fails

A failed onchanges/onchanges_any target was classified the same as a
failed require/watch target, causing the dependent state to hard-fail
instead of being skipped with result=True per the documented truth
table. Also relocates test_slots_documented.py so its `state` fixture
can resolve, and adds a regression test locking in prereq's already
correct skip-on-failed-target behavior.

* Fix inconsistent heading levels in highstate.rst breaking docs build

The "Highstate Output" section's eight subsections (state_output,
state_verbose, state_output_diff, state_output_pct, state_output_profile,
state_tabular, state_compress_ids, Choosing a mode) used "~" underlines,
which docutils resolves to heading level 4 since "~" had not appeared
earlier in the document. Their parent section is level 2 ("="), so this
skipped level 3 entirely, and Sphinx's man-page build (which runs with
-W, treating warnings as errors) failed with eight "Inconsistent title
style: skip from level 2 to 4" errors, breaking the "Generate MAN Pages"
CI job.

Change the eight subsection underlines from "~" to "-", matching the
level-3 convention already used by every other subsection in this file
(e.g. "Top file", "Include declaration"), restoring a consistent
level 1/2/3 hierarchy with no skipped level.

* Fix undefined :ref: label 'beacon' in standalone_minion.rst

The tutorial referenced :ref:`beacons <beacon>`, but no label named
"beacon" exists; the beacons index page defines the label "beacons"
(doc/topics/beacons/index.rst:1), matching how every other doc page
links to it (e.g. doc/topics/development/modules/index.rst:181). The
typo tripped Sphinx's -W (warnings-as-errors) html build with
"WARNING: undefined label: 'beacon' [ref.ref]".

* Fix IndexError in State.__eval_slot for non-dotted accessor

When a slot expression has no dotted post-`)` accessor but has trailing
whitespace, `return_get` was a truthy non-empty string that entered the
dict-traversal branch and crashed on `split(".", 1)[1]`. Guard the
branch on `.` being present instead.

Also strip surrounding matching quotes from `~`-appended text so that
documented examples like `~ "/suffix"` produce `/suffix`, not
`"/suffix"`.

Fixes saltstack#69661

* Replace macos-14 arm64 runner with macos-15

macos-14 begins deprecation on 2026-07-06 and is unsupported after
2026-11-02 per actions/runner-images#13518

Ref: actions/runner-images#13518

* Fix Windows slot documented tests dropping path backslashes

The two tests in tests/pytests/functional/states/test_slots_documented.py
embed a tmp_path directly into the SLS source, e.g.::

    - name: __slot__:salt:test.echo(D:\a\_temp\...\slots_marker_arg)

State.__eval_slot dispatches the ``test.echo(...)`` call through
salt.utils.args.parse_function, which is built on top of
``shlex.shlex(posix=True)``. In POSIX mode shlex treats a backslash as a
generic escape character, so on Windows every backslash in the path was
consumed and the slot resolved to
``D:a_temppytest-of-...slots_marker_arg`` -- a relative path -- so
file.managed rejected it with "not an absolute path" and the assertion
that the marker file was created failed.

Format the paths through ``PurePath.as_posix()`` before splicing them
into the SLS. Windows accepts forward-slash separators for filesystem
paths, and shlex leaves ``/`` untouched, so the slot now resolves to the
same path the assertion checks. Behavior on Linux is unchanged (already
forward slashes).

---------

Co-authored-by: Daniel A. Wozniak <daniel.wozniak@broadcom.com>
* gitfs walkthrough: drop EOL Ubuntu 14/Debian Wheezy/CentOS 7.3 notes
  and pin pygit2>=1.13.1, GitPython>=3.1.50 (matches base.txt + CI
  lockfiles). Adds a GitLab subsection covering deploy tokens,
  project access tokens, PATs, and SSH deploy keys.
* git_pillar docstring: enumerate supported remote URL forms
  (https://, ssh://, scp-style user@host:path, file://). The scp-style
  colon is the most common source of 'Failed to resolve address'
  errors.
* s3fs docstring: document s3.location / s3.service_url /
  s3.https_enable / s3.path_style / s3.verify_ssl with a Regional
  endpoints section, citing the SigV4 redirect failure mode.
* file_roots.rst: new section explaining why /srv/salt is the
  default. Update netconfig/napalm_network examples to use /srv/salt
  instead of /etc/salt/states so the example matches the
  recommendation.
* New tests: tests/pytests/functional/fileserver/gitfs/
  test_documented_providers.py and
  tests/pytests/unit/fileserver/test_s3fs_documented_options.py
  pin the docs to the loader.

Closes saltstack#62260
Closes saltstack#56127
Closes saltstack#60809
Closes saltstack#60408
Closes saltstack#53746
…th tomllib) (saltstack#69607) (saltstack#69608)

* Fix pkg.list_holds returning empty on dnf5 (onedir lacks 'toml')

_list_holds_dnf5 parsed /etc/dnf/versionlock.toml via
salt.serializers.tomlmod, which depends on the third-party 'toml'
library. 'toml' is a CI-only dependency and is not bundled in the onedir
packages, and the onedir ships Python 3.10 (no stdlib tomllib), so the
parse failed silently and pkg.list_holds always returned []. With
hold: True this made pkg.installed re-hold packages on every run.

Parse with the standard-library tomllib (available once the onedir
ships Python 3.11 in 3006.27, see saltstack#69526), falling back to the toml
serializer on older interpreters where it is installed.

Fixes saltstack#69607

* Add unit tests for yumpkg.version_cmp, group_diff, and list_repos

Expand test coverage of salt/modules/yumpkg.py beyond the saltstack#69607 change,
per the contributor policy of improving coverage in modules a PR touches:

- version_cmp: assert it delegates to lowpkg.version_cmp with ignore_epoch
- group_diff: assert group members are split into installed/not-installed
- list_repos: assert .repo files are parsed, non-repo files skipped, and
  each repo records its source file
twangboy and others added 13 commits July 22, 2026 15:58
…saltstack#69841)

Salt never removed installer/uninstaller files downloaded by pkg.install
and pkg.remove, causing the minion cache to grow unbounded (saltstack#69817). Add
an opt-in winrepo_installer_cache_expire minion option that removes
tracked installer cache files older than the configured age each time
pkg.refresh_db runs. Disabled by default.
… cross-origin redirect) (saltstack#69846)

SimpleAsyncHTTPClient forwarded the Authorization/Cookie headers and
auth_username/auth_password to a different origin when following a
redirect, because the redirected request's headers were only ever a
shallow-copy alias of the original request's headers. Explicitly copy
the headers before mutating them, and strip credentials when the
redirect target's scheme/host/port differs from the original request.

Fixing the aliasing bug also exposed a latent issue: the existing
Content-Length/Content-Type/etc. stripping for 302/303-to-GET
redirects deleted from the original request's headers instead of the
new request's, which only worked by accident because of the aliasing.
Both now correctly target the new request.
…tack#69854)

pip 25.2's vendored urllib3 (1.26.20) carries two CVEs (CVE-2025-66418,
CVE-2026-21441), so tools/pkg/build.py worked around this by downloading
pip 25.2, hand-patching its vendored urllib3/_version.py and
urllib3/response.py with unified diffs stored in
pkg/patches/pip-urllib3/, and force-installing that patched wheel into
every onedir build (macOS standalone, onedir_dependencies, and
salt_onedir, which also covers the debian/rpm/windows packages that
consume its output).

pip 26.1.2 already vendors a genuine urllib3 2.6.3 containing the real
upstream fixes for both CVEs, making the hand-patch unnecessary and,
since it's a unified diff against pip 25.2's exact 1.26.20 source,
unable to apply cleanly to 26.1.2 anyway. Replace
_build_patched_pip_wheel/_patch_pip_wheel_urllib3/_apply_unified_diff
with a plain _download_pip_wheel() that pulls pip==26.1.2, update the
three call sites, and remove pkg/patches/pip-urllib3/.

tests/pytests/pkg/integration/test_pip_urllib3_patch.py only existed
to verify the hand-patch was applied; delete it rather than keep
assertions pinned to pip's internal vendoring choices.

requirements/constraints.txt's pip == 26.0.1 dev/lint tooling pin is
untouched here -- it's already past 25.2 and unrelated to the packaged
pip this change bumps.

Fixes saltstack#69852
…altstack#69753) (saltstack#69805)

Scheduled highstate on 3006.27 minions intermittently failed with
``SaltClientError: Nonce verification error`` raised from
``Crypticle.loads``. Two independent races contribute:

(a) ``salt/channel/client.py`` -- ``_do_transfer`` looked up
    ``self.auth.session_crypticle`` twice, once for ``dumps`` on the
    send path and once for ``loads`` on the receive path. A concurrent
    re-auth could swap the reference between the two, causing the reply
    to be decrypted with a different key or session than the request
    was encrypted with. Pin the ``session_crypticle`` reference at send
    time and pass it through ``_package_load`` so both halves of the
    transfer use the same object. Also wrap ``_crypted_transfer`` and
    ``crypted_transfer_decode_dictentry`` in a per-channel
    ``tornado.locks.Lock`` so concurrent coroutines driving the same
    channel cannot interleave their (send, decrypt-reply) windows.

(b) ``salt/transport/zeromq.py`` -- the minion/syndic daemon branch of
    ``_init_socket`` built the ZMQ IDENTITY as
    ``salt-req/{role}/{minion_id}/{slot}`` where ``slot`` came from a
    process-lifetime ``itertools.count()``. That counter's state is
    inherited across ``fork()``, so two concurrent scheduled-job forks
    each draw the same slot value on their first ``next()`` call.
    Combined with the master's ``ROUTER_HANDOVER=1``, siblings claimed
    the same routing-id and in-flight replies queued for one child were
    re-routed to the sibling, decrypting cleanly (same session key) but
    failing the nonce check. Add ``os.getpid()`` to the identity so
    forked children are disambiguated by pid. Separately, widen the CLI
    branch's identity slot from 8-bit ``pid % 256`` to a per-process
    24-bit ``secrets.randbits(24)`` cached at import time so bursty
    ``salt-call`` invocations from the same shell do not birthday-collide
    on the master's ROUTER.

(c) ``salt/crypt.py`` -- backport the diagnostic message from master
    that includes the received and expected nonces in the
    ``SaltClientError`` string, so future field reports of this class
    can distinguish a true crossed reply (two uuid4 hex strings) from a
    replay or a zero-nonce default.

Live end-to-end validation on v3006.27 master + v3006.27 minion under
the same driven load (three 3-second schedules + 8-way concurrent
``salt-call state.highstate`` + 6-way ``saltutil.refresh_pillar`` /
``mine.update`` / ``cp.list_master`` / ``state.show_top`` waves):
baseline 9 ``Nonce verification error`` events in 240 s dropped to 0
events in 434 s across 7 master AES rotations and 128 scheduled
highstate jobs.

Fixes saltstack#69753
In batch mode the salt CLI computed its exit code by iterating batch.run(),
which yields nothing when the target matches no minions (Batch.run returns
early on an empty minion list). retcode stayed 0 and the CLI exited 0, while
the non-batch path exits 2 ("No return received") for the same case
(saltstack#57357). Mirror the non-batch behavior: when no minions matched, print
"No return received" and exit 2.

Fixes saltstack#57357
test_dockercompose.py and test_keystone_role_grant.py came forward from
3006.x, but their source modules were purged from 3007.x by dc526dc
'Initial purge of community extensions'. pylint E0611 fails on the
now-broken imports.
test_linux_shadow: skip the 'crypto' variant of test_gen_password when
the stdlib crypt module is unavailable (removed in Python 3.11+). The
tests were previously masked by a module-level importorskip('spwd')
which the merge removed; that guard is no longer valid because
salt.modules.linux_shadow was decoupled from spwd in saltstack#69651.

conftest (rest_tornado): add 'keys.cache_driver' to the app_mock opts so
saltnado tests that construct CkMinions (which now instantiates
salt.key.get_key on 3007.x+) don't hit KeyError.
- test_master.py: convert async publish tests to async def + await
  (ClearFuncs.publish is async on 3007.x+, was sync on 3006.x)
- test_crypt.py::test_authenticate_missing_creds_attribute_67947:
  adopt the 3007.x gen_keys(keysize) signature + write PEMs to disk,
  and add keys.cache_driver to opts
- test_jinja_custom_filters.py::test_regex_search/match_no_group:
  update expected value to match 3007.x's intentional filter change
  (ff28cd0, ungrouped matches return (match_str,) not ())
- test_client.py::test_do_transfer_serialized_by_lock: construct the
  Future() inside the coroutine (unbundled tornado requires a running
  loop)
- test_cloud.py::test_vm_config_merger_with_overrides: wrap the
  overrides payload as {name: {...}} to match Cloud.vm_config's
  per-VM keying on 3007.x
- salt/cluster/consensus/raft/scheduler.py: nudge colliding timeouts
  by 1e-9 s to prevent dict-key collision when two nodes' random
  follower_timeouts happen to land on the same millisecond (fixes
  pre-existing flake exposed by test_cluster_log_entry)
- rest_tornado/conftest.py app_mock: add __role=master (salt.key.get_key
  now instantiated on 3007.x+ via CkMinions rejects empty __role with
  ValueError).
- test_documented_providers.py base_opts: add gitfs_proxy and
  gitfs_depth to satisfy PER_REMOTE_OVERRIDES on 3007.x
  (init_remotes hits failhard when either is missing from global opts).
Removes changelog entries whose fixes target files that no longer exist
after the initial purge of community extensions (dc526dc) and the
removal of the vendored tornado tree:

Vendored tornado (salt/ext/tornado/ purged):
- 69845: CVE-2026-49853 SimpleAsyncHTTPClient redirect header stripping
- 69848: CVE-2026-49855 _GzipMessageDelegate gzip bomb protection

Extension purge (files no longer in tree):
- 52220: keystone_role_grant state
- 54122: grafana4_datasource state
- 55143: DigitalOcean cloud driver
- 60408: s3fs fileserver docs
- 61082: poudriere module
- 63051: dockercompose module (python_on_whales backend)
- 65233: metadata grain
- 68961: vault functional test container
- 69529: s3fs fileserver race
@twangboy twangboy added this to the Chlorine v3007.15 milestone Jul 30, 2026
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.