Skip to content

Fix non-deterministic pillar rendering across pillar_roots envs (#44937)#69598

Open
dwoz wants to merge 1 commit into
saltstack:3006.xfrom
dwoz:dwoz/fix/issue-44937-pillar-saltenvs-order
Open

Fix non-deterministic pillar rendering across pillar_roots envs (#44937)#69598
dwoz wants to merge 1 commit into
saltstack:3006.xfrom
dwoz:dwoz/fix/issue-44937-pillar-saltenvs-order

Conversation

@dwoz

@dwoz dwoz commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

What does this PR do?

Fixes #44937 - non-deterministic pillar rendering when a minion is
matched by top.sls files in multiple pillar_roots environments.

Pillar.get_tops collected saltenvs into a set and iterated them
in hash order. Because CPython randomizes PYTHONHASHSEED per
process, the order in which top files were processed (and therefore
which env's pillar data won on key conflicts) changed between
salt-call invocations.

Why the existing fix is not enough

Commit b091998 (Jan 2021, "Preserve order of pillar_roots in
_get_envs()") was intended to fix this class of bug for #24501. It
made _get_envs return an ordered list. However the caller in
get_tops immediately wrapped that list back into a set:

saltenvs = set()
...
saltenvs.update(self._get_envs())   # ordering thrown away
...
for saltenv in saltenvs:            # hash-order iteration
    top = self.client.cache_file(self.opts["state_top"], saltenv)

The defect was present and byte-identical on 3006.x, 3007.x, 3008.x,
and master.

What this PR changes

Replaces the set with an insertion-ordered dict, so iteration
follows _get_envs order (which itself follows pillar_roots
config order). The pillar_source_merging_strategy: none branch
preserves its intersection-with-saltenv semantic while keeping source
ordering.

Tests

Adds a unit regression test test_pillar_get_tops_iterates_envs_in_pillar_roots_order
that:

  • builds four pillar_roots envs whose top.sls all match the same
    minion
  • calls Pillar.get_tops()
  • asserts the returned tops keys are in _get_envs order

The test fails reliably on unpatched 3006.x (e.g.
['baz', 'base', 'bar', 'foo'] != ['base', 'foo', 'bar', 'baz'])
and passes 20/20 with the patch under PYTHONHASHSEED=random.

The user-supplied repro script from the issue (10 iterations of
salt-call --local pillar.items) is also stable after the patch
(50/50 deterministic output) where it produced mixed results before.

Related

Merge-forward

The same defect exists on 3007.x, 3008.x, and master; this PR targets
3006.x as the oldest supported branch so the fix can be
merged-forward.

Commits signed-off?

N/A — Apache 2.0 contribution, no DCO required.

Pillar.get_tops collected saltenvs into a set and iterated them in hash
order, so top-file processing depended on PYTHONHASHSEED and varied
between salt-call invocations. Commit b091998 (Jan 2021) made
_get_envs return an ordered list to fix issue saltstack#24501, but the caller
immediately wrapped the result back into a set, throwing the order
away. The defect was present and identical on 3006.x, 3007.x, 3008.x,
and master.

Switch saltenvs to an insertion-ordered dict so iteration follows
_get_envs order (which itself follows pillar_roots config order). The
"pillar_source_merging_strategy: none" branch keeps its
intersection-with-saltenv semantic but preserves the source ordering.

Add a unit regression test that builds four pillar envs all matching
the same minion and asserts get_tops returns them in _get_envs order.

Fixes saltstack#44937
Helps saltstack#24501
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