SaltStack macros and environment variables - macros

I'm trying to create a formula to manage MySQL accounts on multiple database servers. I'm trying to re-use a macro from a macro.sls file, and use environment variables to specify connection settings. However, putting environment files in the state files for each database server doesn't work; it only works when I put them in the macro.sls file. This is not desired since I want to be able to update users on multiple database servers at once. See below:
# Example of "db_user" macro in mysql_user-mgmt/macro.sls
{%- macro db_user(user, password_hash, require_state="None") %}
{{ user }}:
mysql_user.present:
- connection_charset: {{ db_charset }}
- connection_host: {{ db_host }}
- connection_pass: {{ db_pass }}
- connection_port: {{ db_port }}
- connection_user: {{ db_user }}
- host: "%"
- password_hash: "{{ password_hash }}"
{%- if require_state != "None" %}
- require:
- {{ require_state }}
{%- endif %}
{%- endmacro %}
Example db-server1.sls state file using macro
# My mysql_user-mgmt/db-server1.sls file
{%- import "mysql_user-mgmt/macro.sls" as macro -%}
{%- set db_charset = salt['environ.get']('SERVER1_DB_CHAR') -%}
{%- set db_host = salt['environ.get']('SERVER1_DB_HOST') -%}
{%- set db_pass = salt['environ.get']('SERVER1_DB_PASS') -%}
{%- set db_port = salt['environ.get']('SERVER1_DB_PORT') -%}
{%- set db_user = salt['environ.get']('SERVER1_DB_USER') -%}
{{ macro.db_user("someUser", "<MYSQL HASHED PASSWORD>") }}
...and the output then I try to run the salt-call state.sls mysql_user-mgmt.db-server1 command
# Output of running `salt-call state.sls mysql_user-mgmt.db-server1`
local:
- Rendering SLS 'base:mysql_user-mgmt.db-server1' failed: Jinja variable 'db_charset' is undefined
/var/cache/salt/minion/files/base/mysql_user-mgmt/macro.sls(4):
---
{%- macro db_user(user, password_hash, require_state="None") %}
{{ user }}:
mysql_user.present:
- connection_charset: {{ db_charset }} <======================
- connection_host: {{ db_host }}
- connection_pass: {{ db_pass }}
- connection_port: {{ db_port }}
- connection_user: {{ db_user }}
- host: "%"
[...]
---

I believe what you should do is to pass variables as a parameters to the marco like this:
{%- macro db_user(
user,
password_hash,
db_charset,
db_host,
db_pass,
db_port,
db_user,
require_state="None"
) %}
{{ user }}:
mysql_user.present:
- connection_charset: {{ db_charset }}
- connection_host: {{ db_host }}
- connection_pass: {{ db_pass }}
- connection_port: {{ db_port }}
- connection_user: {{ db_user }}
- host: "%"
- password_hash: "{{ password_hash }}"
{%- if require_state != "None" %}
- require:
- {{ require_state }}
{%- endif %}
{%- endmacro %}
and then call a macro like this:
{%- import "mysql_user-mgmt/macro.sls" as macro -%}
{%- set db_charset = salt['environ.get']('SERVER1_DB_CHAR') -%}
{%- set db_host = salt['environ.get']('SERVER1_DB_HOST') -%}
{%- set db_pass = salt['environ.get']('SERVER1_DB_PASS') -%}
{%- set db_port = salt['environ.get']('SERVER1_DB_PORT') -%}
{%- set db_user = salt['environ.get']('SERVER1_DB_USER') -%}
{{ macro.db_user(
"someUser",
"<MYSQL HASHED PASSWORD>",
"db_charset",
"db_host",
"db_pass",
"db_port",
"db_user"
) }}
I'm writing this code from my head - so there might be some errors in syntax, but the logic should be correct.

#alexK Thanks for the answer, that helped greatly! In the end I defined connectionas a YAML map, and let the macro split it up into the corresponding settings.
db-server1.sls:
{%- import "mysql_user-mgmt/macro.sls" as macro -%}
{%- load_yaml as connection -%}
charset: {{ salt['environ.get']('PRODUCTION_DB_CHAR') }}
host: {{ salt['environ.get']('PRODUCTION_DB_HOST') }}
pass: {{ salt['environ.get']('PRODUCTION_DB_PASS') }}
port: {{ salt['environ.get']('PRODUCTION_DB_PORT') }}
user: {{ salt['environ.get']('PRODUCTION_DB_USER') }}
{%- endload -%}
{{ macro.db_user("someUser", "<MYSQL HASHED PASSWORD>", connection) }}
macro.sls
{%- macro db_user(user, password_hash, connection, require_state=False) %}
{{ user }}:
mysql_user.present:
- connection_charset: {{ connection.charset }}
- connection_host: {{ connection.host }}
- connection_pass: {{ connection.pass }}
- connection_port: {{ connection.port }}
- connection_user: {{ connection.user }}
- host: "%"
- password_hash: "{{ password_hash }}"
{%- if require_state %}
- require:
- {{ require_state }}
{%- endif %}
{%- endmacro %}
Same thing in the end. Thanks again!

Related

How can I show form error one after the other ? (Condition 'elseif' not working)

I don't know how to show one form error one after the other when fields are empty : it worked for email, but not for password, gender, ect... I believe it comes from the condition I created because I get the proper error message when I remove them and paste them just outside the condition.
Where is be the mistake ?
register.html.twig
{% extends 'base.html.twig' %}
{% block main %}
{{ form_start(userform, {'attr': {'novalidate': 'novalidate'}} ) }}
{% set formErrors = userform.vars.errors.form.getErrors(true) %}
{{ dump(userform.vars.value.password|length) }}
{% if formErrors|length %}
<div class="alert alert-danger text-center" role="alert">
{% if userform.vars.value.email == null or userform.vars.value.email != 'email' or userform.vars.value.email != 'unique' %}
{{ form_errors(userform.email) }}
{% elseif userform.vars.value.password|length < 6 %}
{{ form_errors(userform.password.first) }}
{% elseif userform.vars.value.gender == null or (userform.vars.value.gender != 'male' and userform.vars.value.gender != 'female' and userform.vars.value.gender != 'non-binary') %}
{{ form_errors(userform.gender) }}
{% elseif userform.vars.value.firstname|length < 2 %}
{{ form_errors(userform.firstname) }}
{% elseif userform.vars.value.lastname|length < 2 %}
{{ form_errors(userform.lastname) }}
{% elseif userform.vars.value.birthdate == null %}
{{ form_errors(userform.birthdate) }}
{% elseif userform.vars.value.occupation|length < 2 %}
{{ form_errors(userform.occupation) }}
{% elseif userform.vars.value.nationality == null %}
{{ form_errors(userform.nationality) }}
{% elseif userform.vars.value.nativelanguage == null %}
{{ form_errors(userform.nativelanguage) }}
{% endif %}
</div>
{% endif %}
{{ form_errors(form) }} is not to do test if field is empty or not or is valid or not but just to display validations messages from Symfony Validator such Assert.
For exmple when you say:
{% if userform.vars.value.email == null %}
yes the first time when you comme on the url of form , the email is always null but this not how we test if email is blank, we don't test it in twig.
Keep in mind that {{ form_errors(form) }} is just for displaying errors messages, just for showing
Let's take this example :
class User implements UserInterface, \Serializable, PasswordAuthenticatedUserInterface
{
#[Assert\NotBlank(message: 'Email ne doit pas ĂȘtre vide')]
#[Assert\Email(message: 'Email n\'est pas valide')]
private ?string $email;
#[Assert\NotBlank]
#[Assert\Length(min: 3, max: 32, minMessage: 'msg min ...', maxMessage: 'msg max ....')]
private $plainPassword;
In User class we have already added a validation test if email is empty or is not valid usnig Assert.
The role of form_erros in twig is just showing this errors messages if they exists but not testing validation
So what you can do in twig is to test if form is valid or not, if not valid so display erros:
{% if not userform.vars.valid %}
{{ form_errors(userform.email) }} // if email is empty or not valid, it will show the message of Assert in user class
{{ form_errors(userform.firstName) }}
{{ form_errors(userform.lastName) }}
{{ form_errors(userform.plainPassword.first) }}
{% endif %}

Recursive Ansible filter in jinja2 template file

I have a playbook like this:
- name: Testing
hosts: localhost
gather_facts: False
tasks:
- name: Set things
set_fact:
my_things:
- parameters:
dog: true
opts:
DO_THIS: true
DO_THAT: "false"
role: Samba
action: dance
- parameters:
cat: true
opts:
DO_THIS: no
DO_THAT: "yes"
role: Funk
action: dance
that I want to write in a jinja2 template that looks like this:
apiVersion: v1
kind: ConfigMap
metadata:
name: "test"
data:
myfile.yml: |+
---
dancers:
{% for thing in my_things %}
- {{ thing.action }}:
{% for key, value in thing.parameters.items() %}
{% if value is string and not (value) %}
{{ key }}: "{{ value }}"
{% elif value is mapping %}
{{ key }}:
{{ value | to_nice_yaml | indent(12) -}}
{% elif value | bool %}
{{ key }}: {{ (value) }}
{% elif not value | bool %}
{{ key }}: {{ (value) }}
{% else %}
{{ key }}: {{ value | to_yaml }}
{% endif %}
{% endfor %}
{% endfor %}
This is doing its thing, but it looks extremly ugly and will also break if there is a mapping inside of a mapping. Ideally, I would like to do something like this:
apiVersion: v1
kind: ConfigMap
metadata:
name: "test"
data:
myfile.yml: |+
---
dancers:
{% for thing in my_things %}
- {{ thing.action }}:
{% thing.parameters | to_yaml %}
This works for role, dog and cat but breaks for opts as it is not recursive.
Since I am deploying this configmap using the operator-sdk and a lookup filter I cannot use jinja's env to write a custom filter.
- name: 'Creating v1.ConfigMap'
community.kubernetes.k8s:
definition: "{{ lookup('template', 'my_template.yml.j2') }}"
Hence, my question would be: Is there any way to inline some python code or alternatively, is there something like a recursive to_yaml filter?

symfony2 and twig: get properties of form field

I am trying to create a form with sub-fields with symfony2.
In twig I render the form as
{{ form_start(form) }}
{{ form_errors(form) }}
<div>
{{ form_label(form) }}
{{ form_errors(form) }}
{% for field in form %}
{{ form_widget(field) }}
{% endfor %}
</div>
{{ form_end(form) }}
However, I want to add some customization depending on the field I am rendering.
What I want to achieve is something like this:
{{ form_start(form) }}
{{ form_errors(form) }}
<div>
{{ form_label(form) }}
{{ form_errors(form) }}
{% for field in form %}
{% if field.label == "myvalue" %} <-- this code is not working
{# do something here #}
{{ form_widget(field) }}
{% endif %}
{% endfor %}
</div>
{{ form_end(form) }}
I am not able to access the label of each of my sub-fields in twig.
I think it is possible with something like
{{ field.vars.something }}
, but I did not manage to find any clear documentation about this.
Can someone please help?
Thank you!
Edit:
I actually found the answer to my question:
It was indeed just
{{ field.vars.label }}
and
{% if field.vars.label == "myvalue" %}
{# do something here #}
{{ form_widget(field) }}
{% endif %}
did the trick.
However, I am still looking for some good documentation about this "vars" attribute in twig, and what can be retrieved with it.
Thanks!
You'll find more information at http://symfony.com/doc/current/reference/forms/twig_reference.html#more-about-form-variables
On that page you'll find a list of common form vars. You might also create custom vars by implementing the buildView method of a FormType. You can read an example at http://symfony.com/doc/current/cookbook/form/create_form_type_extension.html#adding-the-extension-business-logic
Hope it'll help

Collection Type Field with a File Field to generate their own respective download link

I having a problem with getting/generating the download link from a Collection Type Field in a form, inside the collection type I have a File Field. Everything works fine with uploading files but in order to generate their own respective download link is where I'm stucked right now.
Is there any way to get the url from the file field? What can I do?
<ul>
<li><ul class="preguntas" data-prototype="{{ form_widget(form.seguimientos.vars.prototype)|e }}">
{% for pregunta in form.seguimientos %}
<li>
{{ form_row(pregunta.seguimientoTipo) }}
{{ form_row(pregunta.fecha_entrega) }}
{{ form_row(pregunta.fecha_prorroga) }}
{{ form_row(pregunta.descripcion) }}
{{ form_row(pregunta.loQueSeEspera) }}
{{ form_row(pregunta.isRecibido) }}
{{ form_row(pregunta.contactos) }}
{{ form_row(pregunta.comentarios) }}
{{ form_row(pregunta.archivo) }}
</li>
{% endfor %}
</ul></li>
This is what I got with {{ dump(pregunta.archivo.vars.value) }}
object(Proxies\__CG__\daci\contratosBundle\Entity\Document)#596 (5) { ["__isInitialized__"]=> bool(true) ["id":protected]=> int(160) ["path":protected]=> string(44) "1feb865f404cba0567e075c76bf6c0b402621e8e.png" ["file":"daci\contratosBundle\Entity\Document":private]=> NULL ["temp":"daci\contratosBundle\Entity\Document":private]=> NULL }
What I want to get is "path" from the object and create the download link
To access to the field I just needed to use {{ pregunta.archivo.value.path }}
From that I created the download link

symfony 2 form_row with 2 fields

I need something like below:
{% block form_row %}
<div class="form_row">
{{ form_label(form) }}
{{ form_widget(form) }}
{{ form_widget(form.field_name + '_previous') }}
{{ form_errors(form) }}
</div>
{% endblock form_row %}
i.e, two form fields in one row; second field name is equal to first_field_name + _previous.
For example, if field name is 'total_cost', then second field will be 'total_cost_previous'.
How can I do that?
i think you can do something like this:
{% set field = field_name ~ '_previous' %}
{{ form_widget(attribute(form, field)) }}