When using jinja2 for SaltStack formulas you may be surprised to find that your global scoped variables do not have ability to be modified inside a loop. Although this is counter intuitive given the scope behavior of most scripting languages it is unfortunately the case that a jinja2 globally scoped variable cannot be modified from an inner scope.
As an example of a solution that will not work, let’s say you have a global flag ‘foundUser’ set to False, then want to iterate through a group of users, and if a condition is met inside the loop, then ‘foundUser’ would be set to True.
# this will not work!!! {% set users = ['alice','bob','eve'] %} {% set foundUser = False %} initial-check-on-global-foundUser: cmd.run: name: echo initial foundUser = {{foundUser}} {% for user in users %} {%- if user == "bob" %} {%- set foundUser = True %} {%- endif %} echo-for-{{user}}: cmd.run: name: echo my name is {{user}}, has bob been found? {{foundUser}} {% endfor %} final-check-on-global-foundUser: cmd.run: name: echo final foundUser = {{foundUser}}
This will render to:
# this will not work!!! initial-check-on-global-foundUser: cmd.run: name: echo initial foundUser = False echo-for-alice: cmd.run: name: echo my name is alice, has bob been found? False echo-for-bob: cmd.run: name: echo my name is bob, has bob been found? True echo-for-eve: cmd.run: name: echo my name is eve, has bob been found? True final-check-on-global-foundUser: cmd.run: name: echo final foundUser = False
So, while you can see that inside the loop, the value of ‘foundUser’ has indeed been modified, once you get outside the loop again, the value of ‘foundUser’ is still False.
This scope behavior seems wrong to those coming from Java, Python, and most other modern programming languages. But it is unfortunately the way jinja2 works.
The workaround is that instead of setting a simple variable directly, you can instead use a dictionary. And although the dictionary object pointer itself cannot change, the key/value pair entries can be modified.
# works because dictionary pointer cannot change, but entries can {% set users = ['alice','bob','eve'] %} {% set foundUser = { 'flag': False } %} initial-check-on-global-foundUser: cmd.run: name: echo initial foundUser = {{foundUser.flag}} {% for user in users %} {%- if user == "bob" %} {%- if foundUser.update({'flag':True}) %}{%- endif %} {%- endif %} echo-for-{{user}}: cmd.run: name: echo my name is {{user}}, has bob been found? {{foundUser.flag}} {% endfor %} final-check-on-global-foundUser: cmd.run: name: echo final foundUser = {{foundUser.flag}}
Which renders as:
# works because dictionary pointer cannot change, but entries can initial-check-on-global-foundUser: cmd.run: name: echo initial foundUser = False echo-for-alice: cmd.run: name: echo my name is alice, has bob been found? False echo-for-bob: cmd.run: name: echo my name is bob, has bob been found? True echo-for-eve: cmd.run: name: echo my name is eve, has bob been found? True final-check-on-global-foundUser: cmd.run: name: echo final foundUser = True
REFERENCES
http://stackoverflow.com/questions/9486393/jinja2-change-the-value-of-a-variable-inside-a-loop
https://github.com/pallets/jinja/issues/164