Skip to content

Fix saltclass literal '^' list-override marker for single class#69759

Open
ggiesen wants to merge 1 commit into
saltstack:3006.xfrom
ggiesen:fix-50755
Open

Fix saltclass literal '^' list-override marker for single class#69759
ggiesen wants to merge 1 commit into
saltstack:3006.xfrom
ggiesen:fix-50755

Conversation

@ggiesen

@ggiesen ggiesen commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

What does this PR do?

Fixes saltclass leaving a literal ^ element in the merged pillar when a list override is defined by a single class and no existing list is present to override.

In salt/utils/saltclass.py, dict_merge only stripped the leading ^ override marker inside the key-present list+list branch. When the key was absent from the target, control fell to else: a[key] = b[key], which plain-assigned the value verbatim. In real usage the override list is nested (pillars dict -> pkgs list), so the absent key is actually the pillars dict; the whole subtree was assigned by reference and the code never descended to the list, so the ^ survived.

The key-absent branch now descends into dicts (processing nested override lists at any depth) and honours a leading ^ on a list. An empty-list guard avoids an IndexError, and the marker is stripped with non-mutating slicing.

What issues does this PR fix or reference?

Fixes #50755

Previous Behavior

A class defining only an override list, for example:

pillars:
  pkgs:
    - ^
    - three

produced pkgs == ['^', 'three'], so the literal ^ leaked into state runs (e.g. The following packages would be installed/updated: ^).

New Behavior

The same class produces pkgs == ['three']; the ^ marker is stripped whether or not a prior list exists to override. Values with no ^ marker are unchanged.

Merge requirements satisfied?

  • Docs: N/A (no user-facing docs change; behaviour now matches documented override semantics)
  • Changelog entry (changelog/50755.fixed.md)
  • Tests written and passing

Tests added in tests/pytests/unit/utils/test_saltclass.py: a direct-altitude test using the production-exact nested shape from expanded_dict_from_minion, a deeper-nesting variant, an inverse test proving the existing key-present override path is unchanged, a marker-free key-absent test, an empty-list guard test, and a plain-extend test.

Commits signed with GPG?: No

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
@ggiesen ggiesen requested a review from a team as a code owner July 10, 2026 17:54
@charzl charzl self-assigned this Jul 10, 2026
@dwoz dwoz added the test:full Run the full test suite label Jul 10, 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.

3 participants