Repository navigation
Fix state.apply ignoring saltenv when autoloading dynamic modules - #69981
matthewdva wants to merge 1 commit into
Conversation
d76a9ea to
b093f55
Compare
|
@twangboy Thanks for the approval in August. This was moved from the 3006.28 milestone to 3008.5 in the post-release sweep without being merged, so it missed 3006.28, 3007.15 and 3008.3. I've merged the latest 3006.x into the branch (clean merge, no changes to the patch); the CI run is waiting on maintainer approval to start. Is there anything else you need from me to get it in? The bug is also present on 3007.x, 3008.x and master. Given the milestone is now 3008.5, should I retarget this to 3008.x, or open separate PRs for 3007.x/3008.x so it doesn't depend on merge-forward? (I noticed #70342/#70343 rescuing 3006.x fixes that didn't make it forward.) I already have a tested 3007.x version of the branch. @EliAndrewC has also confirmed the fix in production on #67069. |
|
Yes, we are no longer accepting bug fixes on 3006.x. Please retarget the 3008.x branch. |
|
Only 3008? How about 3007, which is really where I'd like to see this land? |
|
We've released a capstone release of 3007.15. That is the final release. Bug fixes now go in 3008.x. 3006.x is CVE only. |
`state.apply saltenv=<env>` (and `state.highstate saltenv=<env>`) could sync
custom modules from saltenvs other than the one requested, and the extra
saltenv would silently overwrite the requested one.
`BaseHighState.load_dynamic()` passed every saltenv in the top file match dict
to `saltutil.sync_all`:
syncd = self.state.functions["saltutil.sync_all"](list(matches), refresh=False)
`matches` is not limited to the requested saltenv. Extra saltenvs get in via
`_master_tops()` results -- which `top_matches()` merged with no saltenv
filter, even though top file sections are filtered against `opts["saltenv"]`
about 25 lines earlier -- and via cross-saltenv `- <saltenv>: <sls>` entries in
a top file.
That alone would be harmless if each saltenv had its own destination, but
`salt.utils.extmods.sync()` copies every saltenv into one flat
`extension_modules/<form>/` directory. There is no per-saltenv separation, so
the last saltenv in the list wins -- and stays winning for subsequent runs.
Reported in saltstack#67069, where `state.apply saltenv=dev` logs the `dev` copy of a
custom state being written and then the `base` copy overwriting it:
Copying '.../files/dev/_states/sideboard.py' to '.../extmods/states/sideboard.py'
Syncing states for environment 'base'
Copying '.../files/base/_states/sideboard.py' to '.../extmods/states/sideboard.py'
This also contradicts the documented behavior in
doc/topics/development/modules/index.rst: "When dynamic modules are autoloaded
via states, only the modules defined in the same saltenv as the states
currently being run are synced."
Changes:
* `load_dynamic()` syncs from exactly `opts["saltenv"]` when a saltenv is
pinned. With no explicit saltenv the previous behavior is kept and every
matched saltenv is synced -- `state.highstate` leaves `opts["saltenv"]` as
`None`, so that path stays reachable.
* `top_matches()` skips `master_tops` data for saltenvs other than the
requested one, matching the filter already applied to top file sections.
* `salt.utils.extmods.sync()` warns when a module name is present in more than
one of the saltenvs being synced, naming the saltenv whose copy wins. The
flat `extension_modules` directory and its last-one-wins precedence are left
as they are; this only makes the collision visible instead of silent.
* Documents the saltenv-pinned behavior and the shared `extension_modules`
directory.
Fixes saltstack#67069
01af046 to
4febc5f
Compare
|
Thanks for clarifying. Retargeted to The branch is now a single commit on top of @twangboy Changing the base dismissed your earlier approval, so this needs a fresh review. CI will also need approval to run. |
What does this PR do?
Makes a saltenv-pinned state run sync dynamic modules (
_modules,_states,_grains, ...) from that saltenv and nothing else.BaseHighState.load_dynamic()passed every saltenv present in the top file match dict tosaltutil.sync_all:matchesis not limited to the requested saltenv. Extra saltenvs get in two ways:_master_tops()results, whichtop_matches()merged with no saltenv filter — even though the top file sections ~25 lines earlier are filtered againstopts["saltenv"];- <saltenv>: <sls>entries in a top file.That would be harmless if each saltenv had its own destination, but
salt.utils.extmods.sync()copies every saltenv into one flatextension_modules/<form>/directory. There is no per-saltenv separation, so the last saltenv in the list overwrites the earlier ones — silently, and it stays overwritten for subsequent runs.This also contradicts what
doc/topics/development/modules/index.rstalready documents:What issues does this PR fix or reference?
Fixes #67069
Previous Behavior
From #67069 —
state.apply saltenv=devwrites thedevcopy of a custom state, then overwrites it with thebasecopy:The net effect, with a custom module
foo.pythat differs betweenbaseandqa:The targeted form works only because
state.slsdoes not autoload; it just uses whatever was cached.New Behavior
state.apply saltenv=qasyncs dynamic modules fromqaonly.salt/state.py—load_dynamic(): whenopts["saltenv"]is set, sync from exactly that saltenv. With no explicit saltenv the old behavior is kept and every matched saltenv is synced.state.highstatewith no saltenv leavesopts["saltenv"]asNone, so that path stays reachable and unchanged.salt/state.py—top_matches(): skipmaster_topsdata for saltenvs other than the requested one, matching the filter already applied to top file sections. Please look at this hunk specifically — it is the one behavior change beyond module syncing. If an ENC /master_topsreturnsbasestates and an operator wants those in asaltenv=qarun, this drops them. It is self-contained and can be dropped if you would rather not change that; theload_dynamic()hunk alone fixes the reported bug.salt/utils/extmods.py—sync(): warn when a module name exists in more than one synced saltenv, naming the winner. Pure logging — the flatextension_modulesdirectory and its last-one-wins precedence are deliberately left alone, since making it per-saltenv would be a much larger change touching the loader. This just makes the collision visible instead of silent.Merge requirements satisfied?
doc/topics/development/modules/index.rstdocuments the saltenv-pinned behavior and the sharedextension_modulesdirectorychangelog/67069.fixed.mdtests/pytests/unit/state/test_load_dynamic.py(6) andtests/pytests/unit/utils/test_extmods.py(4)The tests were verified to be genuine regression tests: with
salt/state.pyandsalt/utils/extmods.pyreverted, 4 of the 10 fail; with the fix, all 10 pass. The other 6 cover the unchanged no-explicit-saltenv path, guarding against over-correcting it.Full
tests/pytests/unit/suite was run with and without the patch on3008.x; the failure sets are identical, so nothing here regresses. (The residual failures are pre-existing on a clean checkout and environment-specific — local pygit2 version, macOS-only tests, zeromq timing.)Notes for reviewers
3006.xper CONTRIBUTING.rst ("the oldest supported branch where the bug exists"); retargeted to3008.xat maintainer request, since3006.xis now CVE-only and 3007.15 was the final 3007 release. The code sites are unchanged onmaster(salt/utils/extmods.pyis byte-identical), and the reporter in [BUG] salt state.apply syncs base modules irrespective of saltenv during salt runs #67069 was running 3006.9.3008.x. The only difference from the3006.xversion is that it no longer touches the existing sentence indoc/topics/development/modules/index.rst: on3006.xthat sentence was incomplete and had to be fixed, while3008.xalready has the complete wording.Commits signed with GPG?
No