rendering SLS 'base:test.test1' failed: Jinja variable 'list object' has no attribute 'sub1' - salt-stack

My SLS file is as below and When trying to access the variable tst1 i get an error, details are as follows
{% load_yaml as test %}
value:
val1: 'string1'
val2: 'string2'
value1: ['sub1','sub2']
{% endload %}
{%- for tst in test.value1 -%}
{% set tst1 = test.value1[tst] %}
{{ tst1 }}
{%- endfor -%}
When trying to access the variable tst1 i get the below error as below.
rendering SLS 'base:test.test1' failed: Jinja variable 'list object' has no attribute 'sub1'
Can anyone help on what exactly the error pointing to ?

{% set tst1 = test.value1[tst] %}
In this line you are trying to access a dict. test.value1 is a list, not a dict.
I think this is exactly what you want:
Code
{%- for tst in test.value1 -%}
{{ tst }}
{%- endfor -%}
Output
sub1sub2

Actually the SLS file should contain IDs (tasks) that are to be executed with modules. Like:
do-something:
module.name:
- args
...
Just putting something like {{ tst }} will not work as it expects a mapping like do-something:
So in the for loop, you should "invoke" some module. I'm using test.echo to show the values in the below example:
{% for tst in test.value1 %}
show-value1-{{ tst }}:
module.run:
- name: test.echo
- text: '{{ tst }}'
{% endfor %}

Related

Unable to create conf file using Jinja - Expected Variables for Nginx

I am learning to use Jinja today to template a config file that I manually created and tested for Nginx. So far the template works as expected and it pulls the dynamic variables as expected. However, I have one line in my conf that needs to be on two separate lines and for some reason, it puts the two host on the same line.
This seems really simple, but I can't seem to spot what is causing it. I've gotten this far so!
My nginx.yml file
---
test_url: abc.{{ nginx_domain }}
nginx_ssl_port: 443
nginx_proxy_conf_dir: /etc/some/place
nginx_ssl_key_dir: /etc/somekey/place
nginx_ssl_cert_dir: /etc/somecert/place
nginx_proxy_log_dir: /etc/some/proxy/place
##Env depends on ansible inventory
test_nginx_proxy_sites:
- name: test
params:
- 'listen {{ nginx_ssl_port }} ssl'
- 'server_name {{test_url}}'
- 'include {{ nginx_proxy_conf_dir }}/conf.d/ssl.conf'
- 'ssl_certificate {{ nginx_ssl_cert_dir }}/{{ nginx_domain }}.crt'
- 'ssl_certificate_key {{ nginx_ssl_key_dir }}/{{ nginx_domain }}.key'
- 'access_log {{ nginx_proxy_log_dir }}/management_access.log'
- 'error_log {{ nginx_proxy_log_dir }}/management_error.log'
locations:
- path: /
location_params:
- 'proxy_pass http://stream_{{ Env }}/'
- 'include {{ nginx_proxy_conf_dir }}/conf.d/proxy.conf'
upstreams:
- name: stream_{{ Env }}
params:
- '{% for host in groups.tag_Class_host %}
server {{ hostvars[host].ipv4_local }}:{{ management_port }};
{% endfor %}
'
My sites.conf.j2
{{ remotely_managed }}
server {
{% if item.blocks is defined and item.blocks|length > 0 %}
{% for block in item.blocks %}
{{ block }}
{% endfor %}
{% endif %}
{% for param in item.params %}
{{ param }};
{% endfor %}
{% if item.locations is defined and item.locations|length > 0 %}
{% for location in item.locations %}
location {% if location.match is defined %}{{ location.match }} {% endif %}{{ location.path }} {
{% if location.root is defined %}
root {{ location.root }};
{% endif %}
{% if location.location_params is defined and location.location_params|length > 0 %}
{% for param in location.location_params %}
{{ param }};
{% endfor %}
{% endif %}
}
{% endfor %}
{% endif %}
{% if item.errors is defined and item.errors|length > 0 %}
{% for error in item.errors %}
{{ error.directive }};
location {{ error.location }} {
{% for param in error.error_params %}
{{ param }};
{% endfor %}
}
{% endfor %}
{% endif %}
}
{% if item.upstreams is defined %}
{% for u in item.upstreams %}
upstream {{ u.name }} {
{% if u.params is defined %}
{% for param in u.params %}
{{ param }}
{% endfor %}
{% endif %}
}
{% endfor %}
{% endif %}
My output
server {
server_name abc.mytest.com;
include /etc/nginx/conf.d/ssl.conf;
ssl_certificate /etc/somecert/place/certs/abc.mytest.com.crt;
ssl_certificate_key /etc/somekey/place/private/abc.mytest.com.key;
access_log /var/log/nginx/management_access.log;
error_log /var/log/nginx/management_error.log;
location / {
proxy_pass http://stream_qa/;
include /etc/nginx/conf.d/proxy.conf;
}
}
upstream stream_qa {
server 1.1.1.09:11111; server 1.1.1.10:11111;
}
The upstream should print out like below:
upstream stream_qa {
server 1.1.1.09:11111;
server 1.1.1.10:11111;
}
Okay - so.
To understand this issue, it's important to know how jinja templating works.
For the solution: Simply put a new line in, like this
{% if item.upstreams is defined %}
{% for u in item.upstreams %}
upstream {{ u.name }} {
{% if u.params is defined %}
{% for param in u.params %}
{{ param }}
{% endfor %}
{% endif %}
The reason for this is due to how jinja templating works.
When rendering jinja, it does not know to put things on a new line, it just knows to put a copy of whatever is in your loop. So if your loop is missing provision for a new line, it will not put things on a new line.
so when you have a loop like yours, or more simply an array of [a, b, c, d, e, f]
{% for i in items %}
{{ i }}
{% endfor %}
it will print as abcdef because the {{i}} literrally means render i here.
By placing a new line in your loop.
{% for i in items %}
{{ i }}
{% endfor %}
it will render i at the end of the last item in the loop, ie on a new line.
In really simple terms, what you want to do is have i also include provision for a new line in your loop so that when jinja renders whats in the loop, its also rendering a new line.
If you look at it like this, the first loop I mentioned renders like this
abcde but the second loop renders like this: a\nb\nc\nd\ne, As you can see, the value for each item in this loop has provision for a new line.
ps: This was really difficult to explain :(
I was able to resolve this issue by editing my nginx.yml file:
upstreams:
- name: stream_{{ Env }}
params:
-
{% for host in groups.tag_Class_host %}
server {{ hostvars[host].ipv4_local }}:{{ management_port }};
{% endfor %}
Rather than looking at Jinja, I should have reviewed YAML. A new pipe in YAML will make my string a multi-string line.
The output matches the output above.

how to comment jinja code in a state sls file (# not working)

i am having trouble commenting out jinja code in a state file,
i have a for loop in sls file
{% for user_name in salt['pillar.get']('userlist') %}
get_user:
- Some code here
....
{% endfor %}
i am commenting it out with #, but the loop still running when i execute state in a minion.
# {% for user_name in salt['pillar.get']('userlist') %}
get_user:
- Some code here
....
# {% endfor %}
what i am missing?
You are commenting jinja code using YAML comment (#), and the reason your for loop is still running is because by default SLS files are rendered as Jinja templates first, and then parsed as YAML documents.
You need to use jinja comment instead, {# ..... #}
{# {% for user_name in salt['pillar.get']('userlist') %} #}
get_user:
- Some code here
....
{# {% endfor %} #}
Enclose them in {# ... #}:
{# {% for user_name in salt['pillar.get']('userlist') %} #}
get_user:
- Some code here
....
{# {% endfor %} #}

Concatenate strings in Jinja?

I'm trying to concatenate strings in a state, and I'm not having much luck. I've seen the posts that suggest using (|join), but all my strings are not in a single dictionary. Here's my code:
sshd_content:
file.line:
{% set admin_groups = '' %}
{% for app in grains['application_groups'] %}
{% for group in pillar['admin_users'][app]['members'] %}
{% set admin_groups = admin_groups ~ ' ' ~ group ~ '#mydomain.com' %}
{% endfor %}
{% endfor %}
- name: /etc/ssh/sshd_config
- match: AllowGroups wheel fred
- mode: replace
- content: AllowGroups wheel fred bob {{ admin_groups }}
I've tried using + instead of ~ without luck, too.
What am I doing wrong?
This state works fine:
sudoers_asmgroups_content:
file.append:
- name: /etc/sudoers.d/mygroups
- text:
{% for app in grains['application_groups'] %}
{% for group in pillar['admin_users'][app]['members'] %}
- '%{{ group }}#mydomain.com ALL=(ALL) ALL'
{% endfor %}
{% endfor %}
I found a viable solution by modifying the solution here.
It appears to be a scoping issue with the admin_groups variable. Not sure why append works, but I'm not going to argue.
For the example in the OP above, here is the code:
sshd_content:
file.line:
{% set admin_groups = [] %}
{% for app in grains['application_groups'] %}
{% for group in pillar['admin_users'][app]['members'] %}
{% do admin_groups.append(group) %}
{% endfor %}
{% endfor %}
- name: /etc/ssh/sshd_config
- match: AllowGroups wheel myadmin
- mode: replace
- content: AllowGroups wheel fred bob {{ admin_groups|join('#mydomain.com ') }}#mydomain.com
{% endif %}
Need to add the second #domain.com since the items are AD group names, and join only adds the separator when there is another value.

How to use variable as a match in saltstack mine.get

I am trying to use a salt mine to get a list of network interfaces of all the minions with the same os as that of the minion on which the jinja template is rendered.
I am trying something like this:
{% set variable = grains['os'] %}
{% set dict = salt['mine.get'('os:variable','network.interfaces','grain') %}
{% for i in dict : %}
// do stuff here
But the problem is in the above salt will try to match os to the value "variable" not to the actual value of the variable.
Using 'os: {{ variable }}' doesn't work too since {{ x }} just prints the value of variable x.
How can I match against the actual os in this case?
You should try + to concatenate prefix and variable name:
{% set variable = grains['os'] %}
{% set dict = salt['mine.get']('os:' + variable,'network.interfaces','grain') %}
{% for i in dict : %}
# do stuff
{% endfor %}

Generate a path appending query string in Symfony2

Is there any facility to generate a path for a given route and arguments, appending the query string automatically? As a temporary workaround i'm using a self made macro:
{% macro path(route, args, with_query) %}
{% spaceless %}
{% set with_query = with_query|default(false) and app.request.queryString %}
{{ path(route, args) ~ (with_query ? '?' ~ app.request.queryString : '' ) }}
{% endspaceless %}
{% endmacro %}
Is there some native function in Symfony2/Twig for doing this?
A nice thing with path Twig extension is that unknow parameters passed through the args array are automatically appended at the end of the URL as GET paramaters :
{{ path('route_id', {'routeParam':'foo', 'unknownParam':'bar'}) }}
will produce
/path/to/route/foo?unknownParam=bar
As simple as :
{{ path('route_id', app.request.query.all) }}

Resources