From cc970892ed88538ca5345dc4f7c536c0c27d14f1 Mon Sep 17 00:00:00 2001 From: Josh Patterson Date: Wed, 11 Nov 2026 10:07:08 -0500 Subject: [PATCH] FIX: keep the app's target when pushing a per-node setting For a setting saved for a single node, push_pillar replaced the app's target from pillar_push_map.yaml with the node id. The app's state was then applied to that node even if the node does not run the app. Seen in the lab: the SOC config test saves elasticsearch.config.cluster.routing.allocation.disk.threshold_enabled for the first node SOC lists, and SOC lists nodes in random order. When that node was a sensor, the elasticsearch state was applied to it and failed to render (so_roles has no so-sensor entry), and so-push-drainer logged "push failed". Narrow the app's target to the node instead of replacing it: "L@ and ( )". A node outside the app's target then matches nothing, and so-push-drainer skips the action. --- salt/reactor/push_pillar.sls | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/salt/reactor/push_pillar.sls b/salt/reactor/push_pillar.sls index 30417d6a2..2063a5744 100644 --- a/salt/reactor/push_pillar.sls +++ b/salt/reactor/push_pillar.sls @@ -7,7 +7,7 @@ # Two branches, keyed on node_id: # A) node_id populated -> the change is scoped to that one minion. Look up the # app in pillar_push_map.yaml and write an intent that runs the app's mapped -# state(s) targeted to just that node. +# state(s) on just that node, and only if the node is in the app's target. # B) node_id empty -> grid-wide app change. Look up the app in # pillar_push_map.yaml and write an intent with the entry's actions as-is. # @@ -116,15 +116,17 @@ def _app_from_setting(setting_id): def _node_actions(entry, node_id): - # Copy the app's mapped actions but retarget each one to the single node. + # Copy the app's mapped actions but narrow each one to the single node. # Preserves the state/highstate selection and any batch/batch_wait overrides. + # The app's own target is kept so the state is not applied to a node that does not + # run the app (e.g. an elasticsearch setting saved for a sensor fails to render there). actions = [] for action in entry: if not isinstance(action, dict): continue node_action = dict(action) - node_action['tgt'] = node_id - node_action['tgt_type'] = 'glob' + node_action['tgt'] = 'L@{} and ( {} )'.format(node_id, action.get('tgt', '*')) + node_action['tgt_type'] = 'compound' actions.append(node_action) return actions