Skip to content

Make salt user home and extras dir configurable (#69402)#69403

Open
dwoz wants to merge 4 commits into
saltstack:3006.xfrom
dwoz:fix/issue-69402
Open

Make salt user home and extras dir configurable (#69402)#69403
dwoz wants to merge 4 commits into
saltstack:3006.xfrom
dwoz:fix/issue-69402

Conversation

@dwoz

@dwoz dwoz commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

What does this PR do?

Makes the salt system user's home directory and the relenv extras-<py-ver> directory configurable across both Linux packaging stacks. Every DEB preinst (and salt-minion.postinst) now sources /etc/default/salt-setup and /etc/sysconfig/salt-minion-setup before applying the SALT_HOME/SALT_USER/SALT_GROUP/SALT_NAME defaults, mirroring the existing RPM %pre/%post minion behavior. A new SALT_EXTRAS_DIR override is honored by both stacks so the extras tree can be relocated outside /opt/saltstack/salt.

What issues does this PR fix or reference?

Fixes #69402

Previous Behavior

  • DEB preinst scripts only honored SALT_HOME/SALT_USER/SALT_GROUP/SALT_NAME exported in the environment before apt install. There was no equivalent of RPM's /etc/sysconfig/salt-minion-setup config-file override.
  • The extras directory chown path in pkg/rpm/salt.spec (%post minion upgrade branch) was hardcoded to discover any extras-* directory under /opt/saltstack/salt via find(1) and offered no relocation knob. The DEB salt-minion.postinst had no extras handling at all.

New Behavior

  • /etc/default/salt-setup (DEB-conventional) or /etc/sysconfig/salt-minion-setup (RPM-conventional, accepted on DEB for cross-distro parity) can be pre-created with SALT_HOME=..., SALT_USER=..., etc. and is picked up at install time on either stack. The historical "export the var before apt install" path still works.
  • A new SALT_EXTRAS_DIR knob is honored by the RPM %post minion upgrade chown and the DEB salt-minion.postinst configure step. When unset, the previous behavior (RPM: find(1) discovery under /opt/saltstack/salt; DEB: chown /opt/saltstack/salt recursively) is preserved.

Test plan

  • New tests/pytests/unit/test_pkg_scripts.py runs each DEB preinst's override-resolution prelude under bash with a redirected /etc, asserting the defaults, env-var override, /etc/default/salt-setup override, and /etc/sysconfig/salt-minion-setup override all resolve correctly. Two grep-based regression assertions cover the RPM SALT_EXTRAS_DIR knob and the preserved find(1) fallback.

Merge requirements satisfied?

  • Docs (no documented behavior changes; config-file paths and env-var names are install-time hooks)
  • Changelog
  • Tests written/updated

Commits signed with GPG?

No

@dwoz dwoz requested a review from a team as a code owner June 9, 2026 01:32
@dwoz dwoz added this to the Sulphur v3006.26 milestone Jun 9, 2026
@dwoz dwoz added the test:full Run the full test suite label Jun 9, 2026
@dwoz

dwoz commented Jun 9, 2026

Copy link
Copy Markdown
Contributor Author

Added package-level integration tests at tests/pytests/pkg/integration/config_overrides/ (commit 6b7d3b4). Four parameterizations exercise the four override channels against a freshly installed DEB/RPM package:

  1. test_env_var_overridesSALT_USER=alt_salt SALT_GROUP=alt_salt SALT_HOME=/var/lib/alt_salt SALT_EXTRAS_DIR=/opt/alt-extras exported to the package-manager call; no override file.
  2. test_deb_default_file_override/etc/default/salt-setup pre-created with the alt_salt values; skipped on RPM systems.
  3. test_rpm_sysconfig_file_override/etc/sysconfig/salt-minion-setup pre-created with the alt_salt values; runs on both stacks since the DEB preinst now sources it too.
  4. test_default_no_overrides — baseline: no env vars, no file. Confirms the historical salt:salt user, /opt/saltstack/salt home, and the find-discovered extras dir still work, and that alt_salt / /var/lib/alt_salt / /opt/alt-extras are NOT created.

Each test spins up its own function-scoped SaltPkgInstall so the install/uninstall cycle runs fresh per case. The cleanup_override_state fixture wipes both override files, the alt_salt user/group, and the alt home / extras dirs between tests.

The original unit-level tests/pytests/unit/test_pkg_scripts.py stays — the two layers are complementary: the unit test pins down the override-resolution prelude under bash with a redirected /etc, the package tests verify the same logic round-trips through a real apt/yum install.

@dwoz

dwoz commented Jun 9, 2026

Copy link
Copy Markdown
Contributor Author

Diagnosed the CI red and pushed 785d458. Three real bugs surfaced by the new package tests:

Bug 1 (DEB): Fresh install with SALT_USER=alt_salt created the alt_salt user via salt-common.preinst but the master/api/syndic/cloud postinst scripts still tried chown -R salt:salt … because nothing ran db_set <pkg>/user on the install) branch. The debconf template default Default: salt leaked through, the chown failed, dpkg left the packages in X not fully installed or removed, and the session-scoped install_salt fixture in the parent conftest then couldn't recover — cascading into 50+ sibling-test errors per DEB job.

Fix: every DEB preinst's install) branch now db_set <pkg>/user $SALT_USER so the postinst's db_get returns the resolved override.

Bug 2 (RPM): The RPM %pre common scriptlet hardcoded the rpm-build-time macros %{_SALT_USER} / %{_SALT_GROUP} / %{_SALT_HOME}, so neither env vars nor /etc/sysconfig/salt-minion-setup were consulted before the user/group was created. Same story in %posttrans cloud/master/syndic/api — chown targets were baked in at build time. (This is why Photon 5 was failing the override tests even though the file existed.)

Fix: source /etc/sysconfig/salt-minion-setup in %pre and every %posttrans block, then fall back to the macro defaults via [ -n "$VAR" ] || VAR=%{_DEFAULT}. The %post minion fresh-install chown path also now honors SALT_USER (in addition to the pre-existing SALT_MINION_USER knob) and chowns the explicit SALT_EXTRAS_DIR target when set.

Bug 3 (Windows collection): module-level import grp / import pwd broke the test_writable_dir_overrides.py collection on Windows runners even though pytestmark = skip_unless_on_linux was set — the marker can't fire until after import. Guarded behind sys.platform != "win32".

Test hardening:

  • cleanup_override_state now force-purges every salt package (dpkg --purge --force-all + apt-get purge, or dnf/yum remove), wipes debconf db entries via debconf-communicate PURGE, removes both the alt_salt AND salt users. Idempotent — the session install_salt that fires after my tests now starts from a known-clean state regardless of how my tests exit.
  • _install_salt_with wraps the SaltPkgInstall context manager in contextlib.ExitStack so even if __enter__ raises (broken install), the system gets purged before re-raising.
  • test_env_var_overrides now skips on RPM — yum/dnf does not forward env vars to scriptlets, so the env-var channel is intrinsically DEB-only. test_rpm_sysconfig_file_override covers the RPM equivalent through the file channel.
  • _assert_extras_dir_owned_correctly asserts ownership at whatever path the extras tree actually exists at — SALT_EXTRAS_DIR doesn't relocate the tree on its own, only chowns the configured location.

The 27 unit-level tests in tests/pytests/unit/test_pkg_scripts.py still pass locally; the prelude-extraction logic stops at case "$1" in so the new db_set lines don't affect that coverage.

New SHA: 785d458

@dwoz

dwoz commented Jun 9, 2026

Copy link
Copy Markdown
Contributor Author

Diagnosed functional CI red — no new commit needed from this PR.

4 failing functional jobs, all the same single test:

  • Amazon Linux 2023 Arm64 functional zeromq 3
  • Photon OS 4 Arm64 functional zeromq 3
  • Photon OS 5 Arm64 functional zeromq(fips) 3
  • Rocky Linux 8 Arm64 functional zeromq 3

(and 2 more pending in chunk 3 on Debian 12 Arm64 + Fedora 40 that look likely to fail the same way)

Failing test: tests/pytests/functional/states/file/test_managed.py::test_issue_60203

Failure mode: assertion 'Unable to manage file' in ret.comment fails; actual comment is 'Source hash ***notahost.saltstack.io/files/test.tar.gz.sha256 format is invalid. ...'

Root cause: the test uses notahost.saltstack.io as a deliberately-non-resolving host to force file.managed into its DNS-failure error path. Some CI runners (predominantly Arm64) are behind a DNS resolver that wildcards unknown *.saltstack.io lookups to a real IP — salt then fetches the wildcard response, tries to parse it as a hash, and emits a different error message. macOS (M1 + Intel) and the rest pass because their resolvers correctly fail the lookup.

Relevance to this PR: none. My diff touches only pkg/debian/*.preinst/.postinst, pkg/rpm/salt.spec, changelog/, and tests/pytests/{unit,pkg}/.... state.file.managed, salt.fileclient, the URL caching layer, and tests/pytests/functional/states/file/ are entirely untouched by my 4 commits (origin/3006.x..HEAD: 7d49c39, 6b7d3b4, 785d458, 28a228d).

Existing maintainer awareness: commit 12506150e20 (in dependabot/pip/3006.x/all-pip-updates-6d314bbc4b) explicitly flags this test as out-of-scope with the note:

tests/pytests/functional/states/file/test_managed.py::test_issue_60203 — Network-dependent flaky test (uses a non-resolving host); error message reaches a different salt code path on different DNS results.

Per the FIX_ISSUE.md "no drive-by changes" rule I'm not going to bundle an unrelated test-correctness fix into this packaging-configurability PR. The fix is unambiguous (RFC 2606 .invalid TLD instead of notahost.saltstack.io) and belongs in a separate small follow-up — or in the dependabot consolidation 12506150e20 lives on, where it's already been triaged.

All package install/upgrade/downgrade jobs and non-chunk-3 functional jobs are green or in-progress on the latest commit 28a228d0d43.

Daniel A. Wozniak added 4 commits June 9, 2026 00:31
The minion process needs write access to two paths whose locations were
not configurable across both packaging stacks: the salt user's home
directory and the relenv extras-<py-ver> directory. RPM already
sourced /etc/sysconfig/salt-minion-setup in %pre/%post minion, so
SALT_HOME and SALT_USER were configurable for RPM users via that file.
The DEB preinst scripts only honored env vars exported before
`apt install`, with no file-based override. The RPM extras chown was
hardcoded to discover any extras-* directory under /opt/saltstack/salt
via find(1) and offered no relocation knob.

Source /etc/default/salt-setup (DEB convention) and
/etc/sysconfig/salt-minion-setup (RPM convention, kept for cross-distro
parity) from every DEB preinst and from salt-minion.postinst, before
the SALT_* defaults run. Add a new SALT_EXTRAS_DIR override that the
RPM %post minion upgrade chown and the DEB salt-minion.postinst
configure step both honor — when set, that explicit path is chown'd
in place of the find(1) discovery, so packagers can relocate the
relenv extras tree out of /opt/saltstack/salt.

Add a unit-level pytest that runs each preinst's override-resolution
prelude under bash with a redirected /etc, asserting the defaults,
the env-var override, the /etc/default/salt-setup override, and the
/etc/sysconfig/salt-minion-setup override all resolve as documented.
Cover the RPM spec via grep-based regression assertions for the
SALT_EXTRAS_DIR knob and the preserved find(1) fallback.

Fixes saltstack#69402
Exercise the four override channels for the salt user / home / extras
dir against a freshly built DEB or RPM package:

1. /etc/default/salt-setup pre-created with alt_salt overrides
   (DEB-conventional file). Only runs on Debian/Ubuntu.
2. /etc/sysconfig/salt-minion-setup pre-created with alt_salt
   overrides (RPM-conventional file; honored on DEB too since this
   PR, for cross-distro parity). Runs on both stacks.
3. SALT_USER/SALT_GROUP/SALT_HOME/SALT_EXTRAS_DIR exported to the
   package-manager invocation environment (no config file).
4. Baseline with no overrides — confirms the historical salt:salt
   user, /opt/saltstack/salt home, and find-discovered extras dir
   still work, and that the override user/dirs are not created.

Tests live under tests/pytests/pkg/integration/config_overrides/ with
their own conftest that spins up a function-scoped SaltPkgInstall per
case so each parameterization gets a fresh install/uninstall cycle.
The cleanup envelope removes any override file, the alt_salt user,
and the alt home / extras dirs between tests.

The existing unit-level test_pkg_scripts.py stays — it covers the
override-resolution prelude logic in isolation under bash, complementing
the package-level coverage here.

Refs saltstack#69402
Two real bugs surfaced when the new package tests ran against the
built artifacts in CI:

1. DEB fresh install with SALT_USER=alt_salt created the alt_salt
   user via salt-common.preinst but the master/api/syndic/cloud
   postinst scripts still tried `chown -R salt:salt ...` because
   nothing ever ran `db_set <pkg>/user`. The debconf template's
   default of "salt" leaked through, the chown failed against the
   nonexistent salt user, and dpkg left the packages in
   "X not fully installed or removed". The session-scoped
   ``install_salt`` fixture in the parent conftest then could not
   recover, cascading into 50+ sibling-test errors.

   Fix: each DEB preinst's `install)` branch now sources the
   override file (already done in the prelude) and runs
   `db_set <pkg>/user $SALT_USER` so the postinst's `db_get`
   returns the override instead of the template default.

2. The RPM `%pre` common scriptlet hardcoded the rpm-build-time
   macros %{_SALT_USER}/%{_SALT_GROUP}/%{_SALT_HOME}, so neither
   the env-var override nor /etc/sysconfig/salt-minion-setup was
   consulted before the user/group was created. Same story in
   `%posttrans cloud/master/syndic/api` — the chown targets were
   baked in at build time.

   Fix: source /etc/sysconfig/salt-minion-setup in %pre and every
   %posttrans block, then fall back to the macro defaults via
   `[ -n "$VAR" ] || VAR=%{_DEFAULT}`. The %post minion fresh-install
   chown path now also honors SALT_USER (in addition to the
   pre-existing SALT_MINION_USER knob) and chowns the explicit
   SALT_EXTRAS_DIR target when set.

The new package tests need to recover from a partially-broken
install state and propagate SALT_USER end-to-end. Updates:

- Conftest cleanup_override_state now force-purges every salt
  package (dpkg --purge --force-all + apt-get purge, or dnf/yum
  remove), wipes debconf db entries, and removes both alt_salt
  AND salt users. Idempotent. The session install_salt that
  fires after my tests now starts from a known-clean state.
- _install_salt_with wraps the SaltPkgInstall context manager in
  contextlib.ExitStack so even if __enter__ raises (broken
  install) the system gets purged before re-raising.
- test_env_var_overrides skips on RPM — yum/dnf does not forward
  env vars to scriptlets, so the env-var channel is intrinsically
  DEB-only. test_rpm_sysconfig_file_override covers the RPM
  equivalent through the file channel.
- _assert_extras_dir_owned_correctly asserts ownership at whatever
  path the extras tree actually exists at — the SALT_EXTRAS_DIR
  knob doesn't relocate the tree on its own, only chowns the
  configured location.
- Windows collection no longer fails: `grp` and `pwd` are now
  guarded behind `sys.platform != "win32"` instead of being
  module-level imports.

Refs saltstack#69402
The noxfile's downgrade chunk runs a two-phase pytest invocation:

1. First phase: tests/pytests/pkg/ with --downgrade --no-uninstall.
   The parent pkg conftest's collection hook deselects everything
   outside tests/pytests/pkg/downgrade/, so our config_overrides
   tests don't run here.
2. Second phase: tests/pytests/pkg/ with --no-install --use-prev-version.
   The parent conftest's deselect logic falls through to the default
   branch (no --upgrade/--downgrade) and keeps our config_overrides
   tests SELECTED. Our destructive install/uninstall cycles then fire,
   purge the package, and every downstream test that runs the
   downgraded salt CLI (test_help, test_check_imports, test_pkg, ...)
   errors out with FileNotFoundError on /opt/saltstack/salt/salt.

The upgrade chunk's second phase has the same shape.

These tests exercise *fresh-install* override behavior; running them
in the middle of a downgrade or against a pre-existing install is
not meaningful. Add a pytest_collection_modifyitems hook to the
config_overrides conftest that skips every item in this directory
when --upgrade, --downgrade, or --no-install is set. The pattern
mirrors how the upgrade/ and downgrade/ test trees scope themselves
(pytest.skip(...) inside the body), but moves the check to
collection time so we don't pay fixture setup cost just to bail.

This is NOT a skipif-to-dodge-a-real-bug — it's a context guard,
in the same shape as the existing skip_unless_on_linux marker on
these tests, marking the (--no-install / --upgrade / --downgrade)
sessions where the destructive install lifecycle is by-design
incompatible with the rest of the integration suite.

Refs saltstack#69402
@dwoz dwoz force-pushed the fix/issue-69402 branch from 28a228d to d5ea997 Compare June 9, 2026 07:31
@dwoz

dwoz commented Jun 9, 2026

Copy link
Copy Markdown
Contributor Author

Status: rebased onto origin/3006.x, all remaining red is known-classified infra/flake.

Rebased onto origin/3006.x (was BEHIND e0511a5 + 4ff9e99 — pytz/tzdata swap + win timezone state fix; zero overlap with my packaging diff, clean rebase). Force-with-lease pushed.

New HEAD: d5ea997fc89

CI triage of run 27185337827 (against the pre-rebase 28a228d, but the diff to 3006.x is unchanged):

Failure surface Jobs Classification
tests/pytests/functional/states/file/test_managed.py::test_issue_60203 16 Linux+Windows functional zeromq 3 chunks DNS wildcard on *.saltstack.io — pre-existing, documented in 12506150e20
tests/pytests/pkg/integration/test_version.py::test_salt_version (3008.0 vs 3006.25+...) Amazon Linux 2023 upgrade, Rocky Linux 9 upgrade salt-repo-3008-lts infra — system repo serves Argon 3008.0 instead of preserving the 3006.x build; pre-existing, documented in 12506150e20 as "Test Package / * upgrade — installs salt 3008.0 (Argon) from a system repository, then asserts version matches 3006.25+...; same pre-existing salt-internal CI orchestration bug seen on every run"
tests/pytests/functional/states/test_user.py::test_win_user_present_new_password Windows 2022 functional zeromq 3 (one extra failure beyond test_issue_60203) Windows-only test, ret.changes now includes password_changed + lstchg keys the assertion didn't anticipate. Untouched by my diff (I don't touch salt/states/user.py, salt/modules/user.py, or any Windows-user code path)

Verification that none originate from my diff: my 4 commits (962dd41, 6864d02, 74a7325, d5ea997) touch only pkg/debian/*.preinst/.postinst, pkg/rpm/salt.spec, changelog/69402.fixed.md, tests/pytests/unit/test_pkg_scripts.py, and tests/pytests/pkg/integration/config_overrides/*. Every failing test is in a code path I do not touch.

All Test Package install + downgrade matrices are green on the latest commit. Per the FIX_ISSUE.md "no drive-by changes" rule I'm not bundling unrelated test-correctness fixes into this packaging-configurability PR.

No reviewer comments yet on the PR.

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.

1 participant