Salt: Jinja2 dictsort not working.

Created on 6 Jul 2015  路  10Comments  路  Source: saltstack/salt

Hello, I'm trying to add a list of users from my pillar. However when I specify the dictionary to be sorted (|dictsort) it's returning the following error.

SaltRenderError: Jinja variable dict object has no element ('Username', {'fullname': 'user name', 'shell': '/bin/bash', 'email_address': '[email protected]', 'homedir': '/home/username', 'uid': 0000})
{%
for account in pillar.user_accounts|dictsort %}{%
   if pillar.user_accounts[account].has_key('email_address')
%}{{ account }} {{ pillar['user_accounts'][account].email_address }}
{% endif %}{%
endfor %}

According to the following document, I should be able to use dictsort and sort by key and case insensitive.

http://jinja.pocoo.org/docs/dev/templates/

Edit: Removing "|dictsort" works however the order isn't always the same so salt thinks that there are always changes.

Bug Core P3 severity-low

Most helpful comment

@AkhterAli

A jinja dictsort returns a tupple that you _must_ unpack. Here is how I use dictsort in jinja;

{% for accu_key, accu_value in accumulator | dictsort -%}
{% for text in accumulator[accu_key] -%}
{{ text }}
{% endfor -%}
{% endfor -%}

Could you try this?

And maybe a tip;

  • -%} or -}} eats all trailing whitespace (incl. newline(s))
  • {%- or {{- eats all leading whitespace (incl. newline(s) from previous line(s))

All 10 comments

Here is a sample of my user_accounts pillar.

user_accounts:
    user1:
        fullname: user one
        email_address: [email protected]
        homedir: /home/user1
        shell: /bin/bash
        uid: 10001
    user2:
        fullname: user two
        email_address: [email protected]
        homedir: /home/user2
        shell: /bin/bash
        uid: 10040

@slimg00dy, thanks for the report. It seems that the problem is that |dictsort is returning an object of the form (<sort-key>, dictionary) rather than just the dictionary itself. You can work around this by either doing for account in pillar.user_accounts|dictsort[1], although I'm not sure if this will work in jinja, or change your pillar dictionary to a list.

@AkhterAli Did you get a chance to try the approach suggested by @jfindlay ?

@cachedout Apologies for the late reply. This has not worked either.

----------
          ID: /etc/postfix/virtusers
    Function: file.managed
      Result: False
     Comment: An exception occurred in this state: Traceback (most recent call last):
                File "/usr/lib/python2.7/dist-packages/salt/state.py", line 1563, in call
                  **cdata['kwargs'])
                File "/usr/lib/python2.7/dist-packages/salt/states/file.py", line 1423, in managed
                  **kwargs
                File "/usr/lib/python2.7/dist-packages/salt/modules/file.py", line 3129, in check_managed_changes
                  **kwargs)
                File "/usr/lib/python2.7/dist-packages/salt/modules/file.py", line 2795, in get_managed
                  **kwargs)
                File "/usr/lib/python2.7/dist-packages/salt/utils/templates.py", line 121, in render_tmpl
                  output = render_str(tmplstr, context, tmplpath)
                File "/usr/lib/python2.7/dist-packages/salt/utils/templates.py", line 314, in render_jinja_tmpl
                  tmplstr)
              SaltRenderError: Jinja syntax error: expected token 'end of statement block', got '['; line 3

              ---
              root [email protected]
              {%
              for account in pillar.user_accounts|dictsort[1] %}{%    <======================
                 if pillar.user_accounts.account.has_key('email_address')
              %}{{ account }} {{ pillar.user_accounts.account.email_address }}
              {% endif %}{%
              endfor %}
              {%
              [...]
              ---

@AkhterAli

A jinja dictsort returns a tupple that you _must_ unpack. Here is how I use dictsort in jinja;

{% for accu_key, accu_value in accumulator | dictsort -%}
{% for text in accumulator[accu_key] -%}
{{ text }}
{% endfor -%}
{% endfor -%}

Could you try this?

And maybe a tip;

  • -%} or -}} eats all trailing whitespace (incl. newline(s))
  • {%- or {{- eats all leading whitespace (incl. newline(s) from previous line(s))

@anonymouz

I just attempted both, it seems like when I add the "-" in the mentioned places, I get the following error.

----------
          ID: /etc/postfix/virtusers
    Function: file.managed
      Result: False
     Comment: Unable to manage file: Jinja variable dict object has no element ('username', {'fullname': 'Firstname lastname', 'shell': '/bin/bash', 'email_address': '[email protected]', 'homedir': '/home/username', 'uid': 10042})
     Started: 21:03:27.488589
    Duration: 21.358 ms
     Changes:

This is what my saltstate looked liked

{% for account in pillar['user_accounts']|dictsort -%}
{%if pillar['user_accounts'][account].has_key('email_address') -%}
    {{ account }} {{ pillar['user_accounts'][account].email_address }}
{% endif -%}
{% endfor -%}

I've also tried

{% for account in pillar['user_accounts']|dictsort %}
{%if pillar['user_accounts'][account].has_key('email_address') %}
    {{ account }} {{ pillar['user_accounts'][account].email_address -}}
{% endif %}
{% endfor %}

It looks up the pillar values just fine but it fails to apply them in the appropriate places for some reason.

@AkhterAli

I'm sorry for the confusion, I only mentioned the "-" for formatting the jinja template and its output. This because of your example formatting, I see it a lot, and most people don't know about the "-" modifier. This is unrelated to your question. Now back to your problem.

I'm not familiar with your pillar layout but my guess is that this should work;

{% for account, values in pillar['user_accounts'] | dictsort -%}
{% if pillar['user_accounts'][account].has_key('email_address') -%}
    {{ account }} {{ pillar['user_accounts'][account].email_address }}
{% endif -%}
{% endfor -%}

Could you try this?
Maybe even !?;

{% for account, values in pillar['user_accounts'] | dictsort -%}
{% if values[account].has_key('email_address') -%}
    {{ account }} {{ values[account].email_address }}
{% endif -%}
{% endfor -%}

@anonymouz This is how my pillar is structured.

user_accounts:
    username:
        fullname: First Name Last Name
        email_address: [email protected]
        homedir: /home/username
        shell: /bin/bash
        uid: 10001
    username:
        fullname: First Name Last Name
        email_address: [email protected]
        homedir: /home/username
        shell: /bin/bash
        uid: 10040

@anonymouz

{% for account, values in pillar['user_accounts'] | dictsort -%}
{% if pillar['user_accounts'][account].has_key('email_address') -%}
    {{ account }} {{ pillar['user_accounts'][account].email_address }}
{% endif -%}
{% endfor -%}

That seems to be doing the trick, let me further confirm and I'll close this issue out.

@anonymouz
Thanks much!

Closing issue.

Was this page helpful?
0 / 5 - 0 ratings