Concatenate strings in Jinja? - salt-stack

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.

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 change a label template using twig inheritance in symfony2

Using Symfony2.3.4 with Twig.
Say I´m trying to add, for example, a colon (:) and, if required, an asterisk (*) to every label of every field in a form generated by Symfony2's CRUD. For this I'm using a twig template to inherit Symfony2's main template form_div_layout.html.twig.
So far:
//config.yml
twig:
form:
resources:
- ::my_form_layout.html.twig
//my_form_layout.html.twig
{% block form_label %}
{% spaceless %}
{% if label is not sameas(false) %}
...
{% set asterisk = '<sup><i title="Campo obligatorio"
class="glyphicon-asterisk" style="color: red; font-size:
8px"></i></sup>' %}
<label {% for attrname, attrvalue in label_attr %}
{{ attrname }}="{{ attrvalue }}"{% endfor %}>
{{ label|trans({}, translation_domain) }}: {% if required %}
{{ asterisk|raw }} {% endif %}
</label>
...
{% endif %}
{% endspaceless %}
{% endblock form_label %}
Problem is this way when I render, for example, a choice-type field for selecting the sex of a person, with expanded and required set to TRUE, the colon (:) and the asterisk (*) appear next to the word Sex AND the words Male and Female as well.
How can I make the template differenciate between the parent and the two children, so the colon and the asterisk appear only after the word Sex.
Thanx
This is how I've done something similar. Modify the 'choice_widget_expanded' block a bit in your custom form layout:
// update this row: {{ form_label(child) }}
{{ form_label(child, child, {'exclude_additions': 'true'}) }}
And update your 'form_label' to check whether this value is defined:
{{ label|trans({}, translation_domain) }}{% if exclude_additions is not defined %}: {% if required %}
{{ asterisk|raw }} {% endif %} {% endif %}

Twig compare two values in different arrays

First of all I'm learning Twig.
I was wondering if it is possible wit Twig to compare two different values from different arrays/lists?!
I have two list of items I call them like so:
{% if page.cart %}
{% for product in page.cart.products %}
{{ product.id }}
{% endfor %}
{% endif %}
And:
{% if products %}
{% for product in products %}
{{ product.id }}
{% endfor %}
{% endif %}
I want to compare both product.id's so I can create a new statement. Is there any way to compare both values? The idea is to check if an id is present in page.cart.products and if so then do something.
I want to create a new statement to display some info. Something like so:
{% if page.cart %}
{% for product in page.cart.products %}
{% set cartId %}{{ product.id }}{% endset %}
{% endfor %}
{% endif %}
{% if products %}
{% for product in products %}
{% set listId %}{{ product.id }}{% endset %}
{% endfor %}
{% endif %}
{% if cartId == listId %}
.... do this ....
{% endif %}
Any help greatly appreciated!
You can loop over one array and check if the id is present in the second one. If it's there, you can do something.
{# In case you want to store them, you can do so in an array #}
{% set repeatedIds = [] %}
{% for productCart in page.cart.products if page.cart %}
{% for product in products if products %}
{% if productCart.id == product.id %}
<p>This id -> {{ product.id }} is already in page.cart.products</p>
{% set repeatedIds = repeatedIds|merge([product.id]) %}
{% endif %}
{% endfor %}
{% endfor %}
{{ dump(repeatedIds) }}
It's a very basic search algorithm and the cost is quadratic. Obviously, there are more efficient ways to look for an element in an array (though more complicated to implement).
If the amount of products you have to deal with is not very big, you could use this solution. However, if you have, let's say, more than one hundred products in each array (or you feel that the algorithm is slowing down your loading time), you could do this process in the controller using more sophisticated methods and PHP and just pass the result to the template.
Hope it helps.

How to pass a variable to form_theme?

I want to theme my form so that the field's label show the current locale, something like
Name (en) :
So I would like to rewrite block generic_label like that :
{# form_theme.html.twig #}
{% block generic_label %}
{% spaceless %}
{% if required %}
{% set attr = attr|merge({'class': attr.class|default('') ~ ' required'}) %}
{% endif %}
<label{% for attrname,attrvalue in attr %} {{attrname}}="{{attrvalue}}"{% endfor %}>{{ label|trans }} (app.session.locale)</label>
{% endspaceless %}
{% endblock %}
and import it in my template :
{% form_theme options 'myBundle:Object:form_theme.html.twig' %}
but the app variable is not accessible in the form template.
How can I pass a variable to a form theme ?
In current version of twig (as for 2016) it is possible.
In your template use the following like this:
{{ form_row(form.content, {'testvar' : 'this is test variable'}) }}
Then, in your theme file, just use:
{{testvar}}
of course instead of form.content you will use the field name you need.
Cheers,
Chris
You need to create a form extension in order to get it done. Take a look at
http://toni.uebernickel.info/2011/11/25/how-to-extend-form-fields-in-symfony2.html
to learn how to create the extension.
To have access to session locale, make sure to inject the container. After that you'll be able to get any var value you want.
If the app variable is not available in the form theme it may be a bug. I suggest you create a ticket.
In the meantime you can use the current template as a theme. Something like...
{% form_theme form _self %}
{% block field_label %}
{% set attr = attr|merge({ 'for': id }) %}
{% if required %}
{% set attr = attr|merge({ 'class': attr.class|default('') ~ ' required' }) %}
{% endif %}
<label{% for attrname, attrvalue in attr %} {{ attrname }}="{{ attrvalue }}"{% endfor %}>{{ label|trans }} ({{ app.session.locale }})</label>
{% endblock %}
If you are using Symfony master (2.1) replace app.session.locale with app.request.locale.

Twig: How to get the first character in a string

I am implementing an alphabetical search.
We display a table of Names. I want to highlight only those alphabets, which have names that begin with the corresponding alphabet.
I am stumped with a simple problem.
How to read the first character in the string user.name within twig.
I have tried several strategies, including the [0] operation but it throws an exception.
Here is the code
{% for i in ['a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z','0-9'] %}
{% set has_user_starting_with_alphabet = false %}
{% for user in pagination %}
{% if user.name[0]|lower == i %}
{% set has_user_starting_with_alphabet = true %}
{% endif %}
{% endfor %}
{% if has_user_starting_with_alphabet %}
<li><span>{{ i }}</span></li>
{% endif %}
{% endfor %}
Is there some function like "starts_with" in twig?
Since twig 1.12.2 you can use first:
{% if user.name|first|lower == i %}
For older version you can use slice:
{% if user.name|slice(0, 1)|lower == i %}
Note: You may also use this notation:
{% if user.name[:1]|lower == i %}

Resources