Skip to content

Merge forward 3006.x into 3007.x - #69880

Open
dwoz wants to merge 113 commits into
saltstack:3007.xfrom
dwoz:merge/3006.x/3007.x-26-07-25
Open

Merge forward 3006.x into 3007.x#69880
dwoz wants to merge 113 commits into
saltstack:3007.xfrom
dwoz:merge/3006.x/3007.x-26-07-25

Conversation

@dwoz

@dwoz dwoz commented Jul 26, 2026

Copy link
Copy Markdown
Contributor

No description provided.

saltbot-open and others added 30 commits July 1, 2026 07:01
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
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
* 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
…ck#68961)

* Use unique Docker container name for vault functional tests

salt_factories.get_container used the fixed name "vault", which causes
Docker 409 conflicts when a stale container remains (failed teardown,
pytest --lf reruns, or runner reuse). Use random_string("vault-") like
other CI container fixtures.

Fixes nightly functional failures creating the vault test container.

Made-with: Cursor

* Add changelog for vault Docker container name fix

---------

Co-authored-by: Daniel A. Wozniak <daniel.wozniak@broadcom.com>
* Fix chocolatey.installed forcing reinstall

chocolatey.installed compared variables which are not the same type
this behaviour causes chocolatey force package reinstall even when
the target version is the same as current.

* Add changelog

* Add changelog content

* Update changelog for chocolatey.installed fix

* Add regression test for chocolatey.installed forcing reinstall (saltstack#68827)

---------

Co-authored-by: AWRZN038 <filip.klocan@raiffeiseninformatik.sk>
Co-authored-by: Daniel A. Wozniak <dwozniak@broadcom.com>
…altstack#69043)

prep_jid() and get_jids() call salt.utils.jid.gen_jid() and
salt.utils.jid.format_jid_instance() respectively, but the module
removed `import salt.utils.jid` in 3006.x. Both calls work today only
because salt.utils.args (transitively pulled in via salt.utils.data)
imports salt.utils.jid as a side effect. Any refactor of that import
chain silently breaks pgjsonb with:

    AttributeError: module 'salt.utils' has no attribute 'jid'

prep_jid() in particular is called from master.py:_prep_jid() on every
job publish, where the caller only handles KeyError/TypeError, so an
AttributeError there would prevent the master from publishing jobs.

Restore the explicit `import salt.utils.jid` and add behavioural unit
tests for prep_jid() and get_jids() (none existed before): jid format,
passed_jid pass-through, and end-to-end formatting of jids-table rows
through the real salt.utils.jid.format_jid_instance().

Refs: saltstack#69042

Co-authored-by: co-cy <rzyw2xsfzm@privaterelay.appleid.com>
…saltstack#69049)

`salt.returners.pgjsonb` had eight error-handling sites in `_get_serv`,
`_purge_jobs` and `_archive_jobs` that wrote `psycopg2.DatabaseError`
messages directly to `sys.stderr` and re-raised:

    except psycopg2.DatabaseError as err:
        error = err.args
        sys.stderr.write(str(error))
        cursor.execute("ROLLBACK")
        raise err

On a daemonized master `sys.stderr` is captured by systemd's journal
(or worse, /dev/null), bypassing the configured `log_file` /
`log_level_logfile` and syslog. Operators that scrape Salt's log file
never see these errors, even though the rest of the file already uses
`log.error` (e.g. `clean_old_jobs`).

The local `error = err.args` assignment is dead -- only `str(err.args)`
is written, then the variable is unused. Also drop `import sys`,
which becomes unused after the migration.

Replace each call with `log.exception(...)` carrying a description of
the operation that failed, so the traceback is preserved in the log.
Behaviour (rollback, re-raise) is unchanged. The asymmetric
`except Exception` block on `_archive_jobs` jids-insert is updated to
the same pattern but kept in place (its scope is PR-7, not this PR).

Add three behavioural tests with `caplog`: one each for `_get_serv`,
`_purge_jobs` and `_archive_jobs` confirming the error reaches Salt's
logger, the transaction is rolled back, and the exception propagates.

Refs: saltstack#69048

Co-authored-by: co-cy <rzyw2xsfzm@privaterelay.appleid.com>
…altstack#69679) (saltstack#69681)

* Let virtualenv.create build stdlib venvs with a specific interpreter

virtualenv.create's venv path always ran the venv module through
sys.executable, which on a onedir minion is Salt's private relenv
interpreter, and it rejected the python argument outright. That left no
way to build an environment for a distro interpreter on systems whose
virtualenv binary is too old to target one (EL8 ships virtualenv 15.1.0
bound to python 3.6 while 3.9/3.11/3.12 are installable alongside).

Honour python on the venv path by running '<python> -m venv', accept a
python interpreter directly as venv_bin, and pass prompt through to
venv (supported since Python 3.6) instead of rejecting it. The
virtualenv-binary path is unchanged, and venv_bin=venv without python
keeps using sys.executable.

Partially created environments are now removed when the creation
command fails, so a failed '<python> -m venv' (e.g. target interpreter
without ensurepip) no longer leaves a bin/python behind that the
virtualenv.managed state mistakes for a working environment. The
obsolete easy_install/ez_setup bootstrap is skipped for venv-module
environments, where ensurepip already provides pip.

The state module already forwarded python; its docs are updated to
match. The legacy unit test file is migrated to the pytest suite with
coverage for the new paths, and functional tests exercise the venv
module end to end. Verified on AlmaLinux 8/9/10, Debian 11/12/13 and
Ubuntu 22.04/24.04/26.04 containers, including cross-interpreter
creation (3.11 minion python building 3.9/3.12/3.14 environments).

Fixes saltstack#69679

* Skip the stdlib-venv functional tests when ensurepip is unavailable

python -m venv bootstraps pip through ensurepip, which is stripped from
the salt onedir/relenv interpreter the CI runners use, so the four
venv-module tests failed with retcode 1 there. Skip them unless the
interpreter has a working ensurepip; they still exercise the code path
under any normal interpreter (verified across AlmaLinux/Debian/Ubuntu).
The forked child added in saltstack#69240 to run saltutil.runner/saltutil.wheel as
the master's configured user had three robustness problems, all on the
drop path (root process, master configured as a different real user):

- daemon=True stopped runners/wheels that spawn their own processes from
  working (e.g. an orchestration containing a parallel:True state), with
  "daemonic processes are not allowed to have children"
- queue.get() had no timeout and the child's liveness was never checked,
  so a child that died before returning a result (OOM kill, os._exit, a
  segfault in a C extension such as libgit2) hung the caller forever
- every child exception was flattened to CommandExecutionError, so the
  drop path diverged from the in-process path -- in particular wheel()'s
  "except SaltInvocationError" could never fire

Run the child non-daemonic and joined; poll the queue and the child's
liveness so a dead child raises instead of hanging; guard the queue
against unpicklable payloads; and re-raise the child's original exception
type where possible.
…#66793) (saltstack#69537)

When fileserver.update is invoked through saltutil.runner or an
orchestration, the runner client injects publisher metadata into the
runner function's kwargs as __pub_* keys (via load_args_and_kwargs).
fileserver.update forwarded those kwargs verbatim to the fileserver
backends, whose update() signatures (e.g. roots.update(),
gitfs.update(remotes=None)) reject unknown keyword arguments, raising:

    Passed invalid arguments: update() got an unexpected keyword
    argument '__pub_user'

Strip the __pub_* publisher metadata with salt.utils.args.clean_kwargs
before forwarding to the backends. This keeps the runner-job user
attribution added in saltstack#63148 intact (it is applied upstream in the
runner client, not via these kwargs).
`_get_serv` calls `psycopg2.connect()` without `connect_timeout`. When
the database is reachable on the network but does not respond
(stalled standby, firewall drop, HAProxy mid-failover), the connect
call blocks for tens of seconds on the system TCP timeout. Since
`event_return` and `clean_old_jobs` both run inside the master event
loop, one such stalled connect cascades into delayed events and
returners across the whole master.

Add `returner.pgjsonb.connect_timeout` (seconds) -- forwarded to
`psycopg2.connect(connect_timeout=...)` only when set. Behaviour for
existing deployments is unchanged: the kwarg is omitted by default,
so libpq's default applies. Operators that need a hard cap opt in
explicitly via master config or `--return_kwargs`.

`_get_options` now also coerces a string-valued timeout (which can
arrive via pillar / environment) to int, mirroring the existing
treatment of `port`.

Add three behavioural tests:

- `test__get_serv_omits_connect_timeout_when_not_configured`
  pins the backwards-compatible default.
- `test__get_serv_passes_connect_timeout_when_configured` verifies
  the kwarg reaches `psycopg2.connect`.
- `test__get_options_coerces_string_connect_timeout_to_int` covers
  the type-coercion path.

Refs: saltstack#69050

Co-authored-by: co-cy <rzyw2xsfzm@privaterelay.appleid.com>
…69616) (saltstack#69617)

etcd_cache.ls() recursed through the whole bank subtree and returned the
flattened leaf key names instead of the bank's immediate children. The
minion data cache stores each minion under minions/<minion_id>, so
cache.list("minions") returned ['data', 'mine', ...] instead of the minion
IDs, and grain (-G), pillar, range, and compound targeting matched no
minions with cache: etcd.

Return only the immediate children, matching localfs and the other cache
backends. Remove _walk(); fold its empty-folder self-reference guard
(saltstack#57377) into ls().
- 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).
Comment thread changelog/69845.fixed.md Outdated
Comment thread changelog/69848.fixed.md Outdated
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
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.