Skip to content

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

Open
dwoz wants to merge 112 commits into
saltstack:3007.xfrom
dwoz:merge/3006.x/3007.x-08-14-26
Open

Merge forward 3006.x into 3007.x#70063
dwoz wants to merge 112 commits into
saltstack:3007.xfrom
dwoz:merge/3006.x/3007.x-08-14-26

Conversation

@dwoz

@dwoz dwoz commented Aug 16, 2026

Copy link
Copy Markdown
Contributor

No description provided.

ggiesen and others added 30 commits July 26, 2026 05:00
…ltstack#69758)

zypper returns exit code 104 when a search matches nothing. The
search-style calls in zypperpkg (search, _get_visible_patterns,
_get_patches, and Wildcard._get_available_versions) run through
cmd.run_all, whose default success_retcodes is [0], so the 104 was
logged as a command failure even though the search itself completed
normally and correctly reported that nothing was found.

Add an ignore_not_found option to the _Zypper wrapper that whitelists
104 via success_retcodes for those search calls, so the exit code is
treated as success and no ERROR is emitted. The found case (0) and all
genuine error codes are left untouched.

Fixes saltstack#58551
…saltstack#67975) (saltstack#69813)

* Support dnf5 group list/info in pkg.group_list and pkg.group_info (saltstack#67975)

Backport of the dnf5 group support from saltstack#67975 to 3006.x, grafted onto the
current group functions so the saltstack#60276 multi-word member-group @-fallback is
preserved.

dnf5 changed the "group list" and "group info" output formats, which the
yum/dnf parser did not understand, so the group functions (and
pkg.group_installed) returned empty or wrong data on Fedora 41+ and
RHEL/AlmaLinux 10.

- group_list: parse the dnf5 "group list --hidden" table by tokenizing each row
  (ID plus the trailing yes/no Installed column) instead of a regex, so a group
  name that contains or ends in the word "yes"/"no" is not mistaken for the
  status column, and a row with trailing whitespace is not dropped.
- group_info: parse the dnf5 "group info" format, where each package section
  carries its first member inline after the colon. That inline member is pulled
  into a dedicated variable rather than mutating the loop's line, blank members
  are skipped, and section lines are fully stripped on both ends.

dnf5 environment and language groups live under a separate "environment"
subcommand and are out of scope here, so those keys stay empty on dnf5.

Validated end to end against live dnf5 5.4.2.1 on Fedora 44: group_list parses
all 157 groups and group_info parses every package with none dropped.

Co-authored-by: Greg Oster <oster@fween.ca>

* Drop the spurious empty optional package from the group_info test

The empty-member guard added in this PR correctly stops a blank line in the
dnf/yum "groupinfo" output from being recorded as an empty-string package, so
the existing test_group_info expectation no longer includes the leading "" in
the gnome-desktop optional list.

---------

Co-authored-by: Greg Oster <oster@fween.ca>
…ooks

Two coupled fixes that keep grouped pip-updates PRs from failing on 3006.x:

1. Python 3.9 caps. Dependabot raises the shared floor of several packages
   to a release that no longer supports Python 3.9 (jaraco.functools 4.5.0,
   jaraco.context 6.1.2, msgpack 1.2.1, more-itertools 11.0.0, pycparser
   3.0, pythonnet 3.1.0, virtualenv 21.5.1, xmldiff 3.0, zipp 4.1.0). With a
   single unmarked floor the py3.9 lock targets become unresolvable. Split
   each into a python_version < '3.10' branch capped at the last
   3.9-compatible release plus an open py>=3.10 branch, mirroring the
   existing cryptography/aiohttp/urllib3 splits. (cryptography and pyopenssl
   already carry all-Python caps here, so they need no split.)

2. Malformed pip-compile hooks. The Py3.13 ZeroMQ hooks (linux/freebsd/
   darwin/windows) and the docs hook were missing their `- id: pip-compile`
   line, so YAML folded them into the preceding Py3.14 blocks as duplicate
   keys and the Py3.14 CI locks never regenerated. Restore the missing id
   lines, correct the py3.14->py3.13 file globs, and regenerate the affected
   Py3.14 locks.
The zenoss.monitored state set ret["changes"] to None on the
already-monitored and failed-add code paths. The state output
validator (OutputUnifier.content_check) requires changes to be a
dictionary and raises "'Changes' should be a dictionary.", so both
paths failed with result=False and that exception comment. Return an
empty dict, which correctly means "no changes" and passes validation.

Fixes saltstack#53966
_gen_keep_files filtered requisites with `"file" in comp`, which for a
bare-string requisite ID degraded to a substring match. Any ID containing
the substring "file" then hit `comp["file"]` and raised
"TypeError: string indices must be integers". Guard the membership test
with an isinstance check so only dict requisites are considered; bare
strings are ignored instead of crashing.

Fixes saltstack#53692 and Fixes saltstack#61042
The '^' list-override marker was only stripped when the target dict
already contained a matching list to override. When a class defined an
override list with no prior list present, dict_merge plain-assigned the
value (including the nested "pillars" dict) by reference and never
descended to the list, leaving a literal '^' element in the merged
pillar. Descend into dicts and honour a leading '^' on a list in the
key-absent branch as well.

Fixes saltstack#50755
salt.utils.network.is_reachable_host only caught socket.gaierror, but
socket.getaddrinfo raises UnicodeError (an idna "label too long" error,
which is not a subclass of gaierror) when a name contains a DNS label
longer than 63 characters. A long salt-ssh -E/--pcre target triggers
this, so _expand_target crashed instead of treating the target as not a
reachable host. Also catch UnicodeError and return False.

Fixes saltstack#57207
Minion.pillar_refresh compiles a fresh pillar and rebinds
self.opts["pillar"] to a new dict. A proxy minion packs the pillar into
its proxy module loader once, at init, by reference
(self.proxy.pack["__pillar__"] = self.opts["pillar"]). The rebind orphans
the dict that pack still aliases, so already-loaded proxy modules keep
serving the pillar they were first packed with until the proxy restarts.

Re-pack the loader with the freshly compiled pillar after the rebind,
guarded so a regular (non-proxy) minion is unaffected. This mirrors the
existing deltaproxy __grains__ re-pack and avoids reload_modules(), so a
module's connection state and __context__ are preserved. Deltaproxy is
covered by the same change: handle_event dispatches each sub-proxy's
pillar_refresh to that sub-proxy instance, so the re-pack runs per
sub-proxy against its own loader and opts.

Out of scope: __opts__, __opts__["pillar"] and values a module copied out
of pillar during init() are load-time snapshots and stay stale under any
non-reload fix.

Fixes saltstack#58197
Minion.pillar_refresh calls module_refresh() at the top, rebuilding the
execution-module loaders (functions/returners/executors/utils) from
self.opts, and only afterwards compiles the new pillar and rebinds
self.opts["pillar"]. Each exec loader snapshots opts["pillar"] by value
when it is built, so those freshly rebuilt loaders capture the OLD pillar.

A regular minion masks this: every job rebuilds the loaders through
gen_modules() before running. A proxy minion's metaproxy job path never
does, so an exec module keeps serving the previous refresh's __pillar__
until the next refresh_pillar -- the "run the same command twice, get two
answers" symptom.

Re-run module_refresh() after the rebind so the exec loaders are rebuilt
against the freshly compiled pillar. It sits next to the saltstack#58197 proxy
re-pack in the success branch and is gated on opts["proxy"], so a regular
minion pays no extra loader rebuild and a failed compile triggers none.

Fixes saltstack#59393
pillar_refresh is a coroutine method, so io_loop.run_sync can call it
directly; the no-arg wrapper lambda tripped pylint's unnecessary-lambda
(W0108) in the lint-tests job.
Pillar compilation loads execution modules (for salt[...] calls in pillar
templates) with the incoming opts when file_client is "local". On the
master that branch is always taken, and the incoming opts carry the
master's id, so salt["match.compound"] and friends matched against the
master rather than the minion the pillar was being compiled for (saltstack#58407).
Load them with self.opts, which __gen_opts stamps with the target
minion_id, matching the non-local branch. Masterless is unaffected
(opts["id"] already equals the minion_id there).

Fixes saltstack#58407
…ted example

The saltstack#58407 fix makes execution modules called during pillar rendering
resolve against the target minion, so the documented match.filter_by
example (which omits the minion_id argument) now correctly matches the
minion and gets the db role. The parametrized expectation still asserted
the pre-fix web* roles, which contradicted the fix and failed in CI.
Adds functional coverage for salt.sdb.env (set/get via environment
variables, including the setdefault no-overwrite behavior) and
salt.sdb.yaml (get with nested/colon traversal, multi-file merge, missing
keys, and the read-only set raising NotImplemented). These are the two sdb
backends that require no external service.

Refs saltstack#61260
salt.utils.data.filter_by ran every lookup_dict key through
fnmatch.fnmatchcase, so a literal key containing glob metacharacters (the
"[" and "]" in GPU/PCI model strings such as "GP104GL [Quadro P4000]")
was parsed as a character class and never matched its own value, falling
through to the default (saltstack#60976). Try an exact string comparison before the
fnmatch fallback. The change is additive: literal keys now match and
existing glob patterns are unaffected. This also covers grains.filter_by,
pillar.filter_by, and match.filter_by, which all delegate here.

Fixes saltstack#60976
wheel.key.gen passed keysize straight to salt.crypt.gen_keys, which feeds
it to rsa.generate_private_key (key_size must be an int). The salt-api
rest_cherrypy POST /keys endpoint passes keysize as a string (cherrypy
form values are always strings), so a request specifying a keysize raised
TypeError and returned a 500 (saltstack#56425). Coerce keysize to an int and
enforce the 2048-bit minimum that gen's docstring already documents.

Fixes saltstack#56425
The file.comment, file.append and file.prepend states read the target
file as bytes and decode it with the system encoding purely to build a
diff. A strict decode raised UnicodeDecodeError and aborted the state
when the file contained bytes invalid in that encoding. Decode with
errors="replace" since the result is only used for the diff.

Fixes saltstack#50903
ggiesen and others added 10 commits August 17, 2026 23:17
…ack#69139)

The LazyLoader put Salt's own source directories on the global sys.path
while a module body executed (__populate_sys_path, and the fpath_dirname
append in _load_module). Any bare import issued while that module ran could
then resolve to a same-named single-file Salt module and get cached in
sys.modules for the life of the process.

That is what broke napalm on modern Salt. Loading salt/utils/napalm.py runs
its top-level `import napalm`, which reaches ncclient.transport, which does a
bare `import ssh` to detect the optional ssh-python/libssh package. With
salt/utils on sys.path that bound to salt/utils/ssh.py (a plain module, not
the ssh-python package), so ncclient's `from ssh.channel import Channel`
raised "'ssh' is not a package", `import napalm` failed, HAS_NAPALM was False,
and the napalm proxy/execution modules never passed their __virtual__ gate.
This is the root cause behind the "Proxymodule napalm is missing an init()"
reports in saltstack#69139 (which saltstack#69330 only improved the error message for).

Salt-internal modules are imported via their fully-qualified salt.* names, so
they never needed sys.path; only external/custom module dirs do, so a custom
module's bare sibling imports keep resolving. Skip appending any directory
under SALT_BASE_PATH in both __populate_sys_path and the fpath_dirname append.

As a side effect, a module whose optional same-named dependency is not
installed no longer "loads" by importing itself.
…ack#69139)

The unit tests monkeypatch SALT_BASE_PATH and use a synthetic shadow file.
This drives the real minion_mods + utils loaders against the real
SALT_BASE_PATH and asserts, from inside a module body executed mid-load, that
no Salt-internal directory is ever placed on sys.path -- the name-independent
guarantee that protects every salt/utils and salt/modules collision (dns,
napalm, git, pip, consul, ...), not just ssh. The concrete salt/utils/ssh.py
shadow is also asserted when a real top-level ssh is absent.
…race

Serialize concurrent NAPALM calls on a shared device connection (saltstack#55332)
Fix handling of GeneralNames and basicConstraints in x509_v2
…adroom-configurable

Add opt-in cgroup-aware minion_memory_headroom / minion_memory_max (saltstack#69884)
…-08-14-26

# Conflicts:
#	.github/workflows/ci.yml
#	.github/workflows/nightly.yml
#	.github/workflows/scheduled.yml
#	.github/workflows/staging.yml
#	.pre-commit-config.yaml
#	cicd/shared-gh-workflows-context.yml
#	pkg/windows/nsis/installer/Salt-Minion-Setup.nsi
#	requirements/base.txt
#	requirements/static/ci/py3.14/darwin.lock
#	requirements/static/ci/py3.14/docs.lock
#	requirements/static/ci/py3.14/freebsd.lock
#	requirements/static/ci/py3.14/windows.lock
#	salt/fileclient.py
#	salt/loader/lazy.py
#	salt/minion.py
#	salt/states/file.py
#	salt/transport/tcp.py
#	salt/utils/event.py
#	salt/utils/http.py
#	tests/pytests/unit/loader/test_loader.py
#	tests/pytests/unit/utils/test_http.py
The revert of PR saltstack#69622 (`2746c4549a0`) removed the mis-merged 3008.x
content from 3007.x's diff, but `git revert -m 1` does not rewrite
history — the 3008.x commits, including the `v3008.1` release commit,
remain reachable in the graph. `salt.version.__discover_version` uses
`git describe --tags --long --match v[0-9]*`, which then picks up
`v3008.1` and reports the branch as `3008.1+714.g<sha>`.

That in turn breaks docs (rst_prolog's ``|current_release_doc|``
substitutes ``/topics/releases/3008.1``, a file that does not exist on
3007.x, and the man build fails with 13 sphinx warnings-as-errors) and
cascades into every downstream build job that depends on the tarball.

Constrain the match to `v3007.*` on this branch so tag detection only
considers 3007.x releases. `git describe` now returns
`v3007.14-<ahead>-g<sha>` as intended.
The three merge-forwarded tests carry 3006.x's `import salt.ext.tornado.*`
which was removed from 3007.x. Rewrite to plain `tornado.*` so lint-tests
passes and the tests can be collected on 3007.x. Same fix that was in the
previous merge-forward attempt, dropped when we reset to the clean
(reverted) 3007.x baseline.
Bump the bundled onedir Python from 3.10.20 to 3.11.15 on 3007.x, matching
the 3006.x change from saltstack#69526. Python 3.10 reaches end of security support
in October 2026, while Salt 3007.x must continue shipping security fixes
past that date. Aligning the bundled interpreter with 3.11 keeps Salt's
security release cadence on a supported CPython.

The change is also load-bearing for CI on this merge-forward: relenv 0.22.18
(picked up from 3006.x) regressed the Windows CPython 3.10 native build,
which surfaces as a libzmq zmq_assert abort on every salt-call subprocess
started from the onedir (STATUS_FATAL_APP_EXIT out of libzmq's zmq.cpp:988
with EINVAL/EAGAIN). The 3006.x nightly proves relenv 0.22.18 + Python
3.11.15 is healthy on Windows.

The cicd shared context drives every workflow's python-version input;
the regenerated ci/nightly/scheduled/staging workflows pick up the new
value via tools/precommit/workflows.py.
The py3.11 CI lockfile pulls jsonschema 4.26.0, which regressed several
message strings the ConfigTestCase suite asserts on:

  * ``is not one of`` (oneOf/anyOf enum failure) -> generic
    ``is not valid under any of the given schemas``
  * ``is too short`` (minItems) -> ``should be non-empty``
  * ``is not allowed for`` (not) -> ``should not be valid under ...``
  * ``'X' is a required property`` when nested inside anyOf -> generic
    ``is not valid under any of the given schemas``

The existing ``JSONSCHEMA_VERSION >= 3.0.0`` branches assumed the 3.x
wording carried forward; narrow them to the 3.x range and let 4.x fall
through to the pre-3.x generic message, and add a new gated branch for
the ``not``/``minItems`` cases.

The hostname and date-time format-checker tests can't run under jsonschema
4.x with only the deps we ship in CI: 4.x delegates ``hostname`` to the
optional ``fqdn`` package and ``date-time`` to ``rfc3339-validator`` /
``isoduration``, and our lockfiles only carry the legacy ``strict-rfc3339``
that 3.x used. Skip them on 4.x with a clear reason rather than pull new
optional deps into the CI matrix.
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.

7 participants