ansible, jinja template with loops, losing newlines [duplicate] - ansible

This question already has answers here:
How do I get an Ansible template to honor new lines after a conditional
(5 answers)
Closed 4 years ago.
Trying to build a JSON file from a template. It works ok as such, but for some reason newlines within loop constructs go missing, which I find rather irksome; the file "works" (is machine readable just fine), but for human consumption it's pretty ill-suited.
Here's my template:
{
"Users":
[
{% for username,user in _vpn_user_accounts.items() %}
{
"Name" : "{{ user.name }}",
"Hash" : "{{ user.hash }}",
"Cns" : [
{% for cn in user.cns.get(server_name, []) %}
"{{ cn }}"{% if not loop.last -%},{% endif %}
{% endfor %}
],
"key_ids" : [
{% for key in user.get( 'keys' , []) %}
{% if key.public is defined %}
"{{ key.public }}"{% if not loop.last %},{% endif %}
{% endif %}
{% endfor %}
],
"comment" : "{{ user.get( 'comment', '' ) }}"
} {% if not loop.last %},{% endif %}
{% endfor %}
]
}
Here's some sample data:
- andrej:
name: "andrej"
hash: "$2b$10$8EF3H.../Wj0RchEqU6"
cns:
h:
- "andrej_linux_h_201808171440"
- "andrej_linuxvm_h_201809131031"
- "andrej_mac_h_201808171441"
- "andrej_phone_h_201808171441"
w:
- "andrej_linux_w_201808171439"
- "andrej_linuxvm_w_201809131031"
- "andrej_mac_w_201808171441"
- "andrej_phone_w_201808171441"
keys:
- name: "andrej"
public: "kbjrvtni"
- name: "andrej2"
public: "ijrltifu"
- name: "andrej3"
public: "rbcvncbt"
comment: "systems"
This is my desired outcome (running against server "w"):
{
"Users":
[
{
"Name" : "andrej",
"Hash" : "$2b$10$8EF3H.../Wj0RchEqU6",
"Cns" : [
"andrej_linux_w_201808171439",
"andrej_linuxvm_w_201809131031",
"andrej_mac_w_201808171441",
"andrej_phone_w_201808171441"
],
"key_ids" : [
"kbjrvtni",
"ijrltifu",
"rbcvncbt"
],
"comment" : "systems guy"
}
]
}
This is what I get:
{
"Users":
[
{
"Name" : "andrej",
"Hash" : "$2b$10$8EF3H.../Wj0RchEqU6",
"Cns" : [
"andrej_linux_w_201808171439", "andrej_linuxvm_w_201809131031", "andrej_mac_w_201808171441", "andrej_phone_w_201808171441" ],
"key_ids" : [
"kbjrvtni", "ijrltifu", "rbcvncbt" ],
"comment" : "systems guy"
}
]
}
I have experimented with #Jinja2: trim_blocks and #Jinja2: keep_newline, neither of which showed the desired result. Well, trim_blocks kind of did, but it also gave me a bunch of empty lines where the jinja conditionals were ... unsatisfactory.
Any hints on how to resolve this? As I said, it works, but it irks me immensely that I can't get human readable, nice output.

And this little change actually made the problem go away in the end.
#jinja2: trim_blocks:False
{
"Users":
[
{% for username,user in _vpn_user_accounts.items() %}
{
"Name" : "{{ user.name }}",
"Hash" : "{{ user.hash }}",
"Cns" : [
{%- for cn in user.cns.get(server_name, []) %}
"{{ cn }}"{% if not loop.last -%},{% endif %}
{%- endfor %}
],
"key_ids" : [
{%- for key in user.get( 'keys' , []) %}
{% if key.public is defined %}
"{{ key.public }}"{% if not loop.last %},{% endif %}
{% endif %}
{%- endfor %}
],
"comment" : "{{ user.get( 'comment', '' ) }}"
} {% if not loop.last %},{% endif %}
{% endfor %}
]
}

Related

AnsibleError: template error while templating string: expected token ':', got

I have the following JSON file.
[
{
"NODE": "ha2(VRM02)",
"ROLE": "active",
"PHASE": "Actived",
"RESS": "normal",
},
{
"NODE": "ha1(VRM01)",
"ROLE": "standby",
"PHASE": "Deactived",
"RESS": "normal",
}
]
Through ansible I have it stored in a variable called "fusionquery1".
Through a template, I am trying to go through it to create a file with some data from the JSON file. Up to this part I don't get error.
{% for item in fusionquery1 %}
{% set item = fusionquery1[loop.index-1] %}
{{item.NODE}},NODE ROLE,NA,OK,cualitativo,igualA,ROLE,{{item.ROLE}}
{% endfor %}
My problem is when I want to add this conditional to the side of the above statement
{% if ({{item.NODE}} == "ha2(VRM02)" and {{item.ROLE}} == "active") or ({{item.NODE}} == "ha1(VRM01)" and {{item.ROLE}} == "standby") %}Ok{% else %}Failed{% endif %}
I get the following error
FAILED! => {"changed": false, "msg": "AnsibleError: template error while templating string: expected token ':', got '}'. String: {% for item in fusionquery1 %}\r\n{% set item = fusionquery1[loop.index-1] %}\r\n{{item.NODE}},NODE ROLE,NA,OK,cualitativo,igualA,ROLE,{{item.ROLE}},{% if ({{item.NODE}} == \"ha2(VRM02)\" and {{item.ROLE}} == \"active\") or ({{item.NODE}} == \"ha1(VRM01)\" and {{item.ROLE}} == \"standby\") %}Ok{% else %}Failed{% endif %}\r\n{% endfor %}\r\n"}
Remove the braces from the conditions, e.g.
- debug:
msg: |
{% for item in fusionquery1 %}
{% if (item.NODE == "ha2(VRM02)" and item.ROLE == "active") or
(item.NODE == "ha1(VRM01)" and item.ROLE == "standby") %}
Ok
{% else %}
Failed
{% endif %}
{% endfor %}
gives
msg: |-
Ok
Ok

How to fix Jinja2 yml indentation

I have the below structure of data source in yml format.
systemEmailAccount:
mode: DEFAULT
username: test#gmail.com
password: Test#123
displayName: "Test"
senderAddress: test#gmail.com
oauthClientId: "xxxxxxx"
oauthSecret: "xxxxxxxxx"
tokenExpires: 1458168133864
secondarySystemEmailAccount:
mode: DEFAULT
username: test2#gmail.com
password: Test#123
displayName: "Test2"
senderAddress: test2#gmail.com
oauthClientId: "xxxxxxx"
oauthSecret: "xxxxxxxxx"
tokenExpires: 14581681338777
I'm trying to regenerate it to a new file using this jinja2 template snippet.
systemEmailAccount:
{% for key,value in config.systemEmailAccount.items() %}
{% if key == "mode" or key == "username" or key == "password" or key == "senderAddress" or key == "tokenExpires" %}
{{ key }}: {{ value }}
{% else %}
{{ key }}: {{ '"' }}{{ value }}{{ '"' }}
{% endif %}
{% endfor %}
secondarySystemEmailAccount:
{% for key,value in config.secondarySystemEmailAccount.items() %}
{% if key == "mode" or key == "username" or key == "password" or key == "senderAddress" or key == "tokenExpires" %}
{{ key }}: {{ value }}
{% else %}
{{ key }}: {{ '"' }}{{ value }}{{ '"' }}
{% endif %}
{% endfor %}
But the indentation does not seem right in the output.
emailAccount1:
username: test#gmail.com
mode: DEFAULT
password: Test#123
displayName: "Test"
senderAddress: test#gmail.com
oauthClientId: "xxxxxxx"
oauthSecret: "xxxxxxxxx"
tokenExpires: 1458168133864
emailAccount2:
username: test2#gmail.com
mode: DEFAULT
password: Test#123
displayName: "Test2"
senderAddress: test2#gmail.com
oauthClientId: "xxxxxxx"
oauthSecret: "xxxxxxxxx"
tokenExpires: 1458168133864
Any suggestion to fix this?
have a look at those 2 topics from official jinja2 docs
white space control: https://jinja.palletsprojects.com/en/2.11.x/templates/#whitespace-control
filter indent: https://jinja.palletsprojects.com/en/2.11.x/templates/?highlight=indent#indent
try this snippet below:
systemEmailAccount:
{%- for key,value in config.systemEmailAccount.items() %}
{%- if key == "mode" or key == "username" or key == "password" or key == "senderAddress" or key == "tokenExpires" %}
{%- filter indent(width=2) %}
{{ key }}: {{ value }}
{%- endfilter %}
{%- else %}
{%- filter indent(width=2) %}
{{ key }}: {{ '"' }}{{ value }}{{ '"' }}
{%- endfilter %}
{%- endif %}
{%- endfor %}
[..]

Pass string as variable name to for loop with nunjucks

I have different sites, with different sitenames and try to get the right array on each site. So basically I want to pass the variable name dynamically to the for loop.
{% set sitename = "user" %}
{% set blockRef = sitename + 'Blocks' %} //result should be userBlocks
{% set userBlocks = [ 'chats', 'profile', 'settings' ] %}
{% set adminBlocks = [ 'chats', 'archive', 'profile', 'settings' ] %}
{% for blockName in blockRef %}
//user values from userBlocks array here
{% endfor %}
However, the passed name is interpreted as text and does not refer to the given array. Is there a way to make my code dynamic?
{% set sitename = "user" %}
{% set userBlocks = [ 'chats', 'profile', 'settings' ] %}
{% set adminBlocks = [ 'chats', 'archive', 'profile', 'settings' ] %}
{% set blocks = userBlocks if sitename == 'user' else adminBlocks %}
{% for blockName in blocks %}
//user values from userBlocks array here
{% endfor %}

How to use ansible template module with variable receiving value from hostvars?

In templates/config.py:
{% if env_value == 'Dev' %}
{% set x = {{hostvars['ces_dev']['ansible_host']}} %}
{% else %}
{% set x = {{hostvars['ces_demo']['ansible_host']}} %}
{% endif %}
API_CONFIG = {
'api_email_url': 'http://{{x}}:8080/api/users/mail',
}
In host inventory:
ces_dev ansible_ssh_private_key=<path> ansible_host=a.b.c.d
ces_demo ansible_ssh_private_key=<path> ansible_host=x.y.z.w
Expected output, if condition is met:
API_CONFIG = {
'api_email_url': 'http://a.b.c.d:8080/api/users/mail',
}
I am getting an error: "msg": "AnsibleError: template error while templating string: expected token 'colon', got '}'
How to resolve this and get the desired output?
I cracked the expected output myself, with several try-hit-error method. The solution is:
API_CONFIG = {
{% if env_value == 'Dev' %}
'api_email_url': 'http://{{hostvars['ces_dev']['ansible_host']}}:8080/api/users/mail',
'api_token_url': 'http://{{hostvars['ces_dev']['ansible_host']}}:8080/api/app/',
{% else %}
'api_email_url': 'http://{{hostvars['ces_demo']['ansible_host']}}:8080/api/users/mail',
'api_token_url': 'http://{{hostvars['ces_demo']['ansible_host']}}:8080/api/app/',
{% endif %}
}
The variables are expanded by default. For example
{% if env_value == 'Dev' %}
{% set x = hostvars.ces_dev.ansible_host %}
{% else %}
{% set x = hostvars.ces_demo.ansible_host %}
{% endif %}
API_CONFIG = {
'api_email_url': 'http://{{x}}:8080/api/users/mail',
}

Format liquid(Shopify) code in visual studio code

How to format .liquid (Shopify liquid) code in Visual Studio.
By settings language as HTML I can do it but at the same time, I can't use Shopify autocomplete. When I switch to liquid.html then I can use the autocomplete but I can't format code. Is there any way I can use another language and format code as another language in visual studio?
The VSCode Liquid extension provides formatting and syntax highlighting. Also has intellisense and ton of other features.
<div class="page-width">
{% if section.settings.title != blank %}
<header class="section-header">
<h2 class="section-header__title">
{{section.settings.title}}
</h2>
</header>
{% endif %}
<div class="popup-gallery">
{%- for media in product.media -%}
{%- liquid
assign has_video = false
assign video_type = ''
case media.media_type
when 'external_video'
assign has_video = true
assign video_type = media.host
if media.host contains 'youtube'
assign video_id = media.external_id
endif
when 'video'
assign has_video = true
assign video_type = 'mp4'
endcase -%}
<div
href="{%- if has_video -%}
{%- if media.media_type=='video' -%}
{%- for source in media.sources -%}
{{- source.url -}}
{%-endfor-%}
{%- else -%}
{%- assign video_url = media | external_video_url -%}
{%- if video_url contains "youtube" -%}
https://www.youtube.com/watch?v={{- media.external_id -}}
{%- else -%}
https://vimeo.com/{{- media.external_id -}}
{%- endif -%}
{%- endif -%}
{%- else -%}
{{- media | image_url -}}
{%- endif -%}"
class="
{% if has_video %}
video
{% else %}
image
{% endif %}"
title="
{% if has_video %}
This is a video
{% else %}
This is a image
{% endif %}
">
{%- assign img_url = media.preview_image | img_url: '1x1' | replace: '_1x1.', '_{width}x.' -%}
<img
class="lazyload"
data-src="{{ img_url }}"
data-widths="[120, 360, 540, 720]"
data-aspectratio="{{ media.preview_image.aspect_ratio }}"
data-sizes="auto"
alt="GALLERY"
>
<noscript>
<img
class="lazyloaded"
src="{{ media | img_url: '400x' }}"
alt="GALLERY"
>
</noscript>
</div>
{%- endfor -%}
</div>
</div>
{{ 'magnific-popup.min.css' | asset_url | stylesheet_tag }}
<script
type="text/javascript"
src="{{ 'jquery.min.js' | asset_url }}"
></script>
<script
type="text/javascript"
src="{{ 'magnific-popup.min.js' | asset_url }}"
></script>
<script type="text/javascript">
$(".popup-gallery").magnificPopup({
delegate: "div",
type: "image",
gallery: {
enabled: true,
navigateByImgClick: true,
preload: [0, 1] // Will preload 0 - before current, and 1 after the current image
},
callbacks: {
elementParse: function (item) {
console.log(item.el[0].className);
if (item.el[0].className == "video") {
(item.type = "iframe"),
(item.iframe = {
patterns: {
youtube: {
index: "youtube.com/", // String that detects type of video (in this case YouTube). Simply via url.indexOf(index).
id: "v=", // String that splits URL in a two parts, second part should be %id% // Or null - full URL will be returned // Or a function that should return %id%, for example:
// id: function(url) { return 'parsed id'; }
src: "//www.youtube.com/embed/%id%?autoplay=1" // URL that will be set as a source for iframe.
},
vimeo: {
index: "vimeo.com/",
id: "/",
src: "//player.vimeo.com/video/%id%?autoplay=1"
},
gmaps: {
index: "//maps.google.",
src: "%id%&output=embed"
}
}
});
} else {
(item.type = "image"),
(item.tLoading = "Loading image #%curr%..."),
(item.mainClass = "mfp-img-mobile"),
(item.image = {
tError: 'The image #%curr% could not be loaded.'
});
}
}
}
});
</script>
{% schema %}
{
"name": "Product gallery",
"class": "product-gallery-section",
"settings": [
{
"type": "text",
"id": "title",
"label": "Heading"
}
]
}
{% endschema %}

Resources