ansible can't get inventory_hostname - jinja2

I'm trying to get the short name of a server being worked on.
I have this in jinja2:
ServerAlias graphite.{{ hostvars[inventory_hostname] }}
ServerAlias graphite.{{ hostvars[inventory_hostname] }}.{{dc}}.{{subnet}}
The above just spills the whole glob of facts instead of just the short name.
This is what the hosts.yaml looks like:
graphite.experimental.com dc=lv1 subnet=coupons.lan

What you want to use is just {{ inventory_hostname }} (or {{ inventory_hostname_short }} for the short name).
The hostvars object is a way to access the variables of every host that Ansible knows about. So hostvars[inventory_hostname] will give you the object containing all known facts about the current host, hostvars['foo'] will give you the object containing all the known facts about the host 'foo', etc.
Suppose you have a group of hosts called 'db_servers' and you wanted to generate a list of the IP addresses of all those hosts in a template. Here's how you would do that:
{% for host in groups['db_servers'] %}
{{ hostvars[host]['ansible_eth0']['ipv4']['address'] }}
{% endfor %}

Related

Use minion targeting within Jinja templates

SaltStack allows for precise targeting of minions on the command-line, e.g.:
salt 'prefix*' grains.items
^^^^^^^
Is there some way to use the same way of targeting minions within Jinja templates? I'd like to iterate over hosts targeted by the given matcher, e.g:
{% for minion in salt.minions['prefix*'] %}
^^^^^^^
Unfortunately I didn't find anything in the official help on using Jinja.
From the additional comments you posted, it seems you are looking for the Salt mine functionality. Using Salt mine we can collect data from minions (to master), and use the data on (usually) other minions.
Salt Mine can be enabled in minion configuration file, or minion's pillar, but format is same.
For the sake of example:
Consider 4 minions as below:
web1.example.local
db1.example.local
db2.example.local
db3.example.local
Now in the pillar file (db.sls) for the dbX minions, I have defined two Salt modules as Mine functions to get hostname and additionally IP address.
mine_functions:
network.get_hostname: []
network.ip_addrs: []
Note that the Mine and Pillar might have to be updated (mine.update)/refreshed (saltutil.refresh_pillar) for this change to reflect.
Now any of the above Mine functions can be referenced by the Mine module's get function. Minions can be targeted in the usual way - wildcard, grains, compound, etc.
Sample example.conf.j2 template where I render the minion id and hostname.
{% for minion_id, hostname in salt['mine.get']('db*', 'network.get_hostname') | dictsort() %}
Minion id {{ minion_id }} has hostname {{ hostname }}
{% endfor %}
This template can be rendered on the web1.example.local with below state:
create-example-conf:
file.managed:
- name: /tmp/example.conf
- source: salt://example.conf.j2
- mode: 0664
- template: jinja
This will result in /tmp/example.conf file with the lines showing minion_id and hostname.

How to Convert a folder of csv files to json files within Jekyll

Is it possible to convert a folder of csv files to json as part of a Jekyll workflow? I currently use a python script to do this but would like to do it entirely within Jekyll
You can call your python script as part of the build process, but you'll have to make a teeny tiny plugin to do it. This also assumes you're not using github pages, because they don't like plugins.
make a _plugins directory in your site root.
Inside that directory, create csv_to_json.rb
In that ruby file, call your python script with the following code:
# The following line tells jekyll to run everything between 'do' and 'end'
# when it finishes writing the site to disk:
Jekyll::Hooks.register :site, :post_write do |_site|
# Backticks are one way to call shell commands from ruby:
`python your_script_here.py` # replace with the correct filename
end
This is untested code. Relevant documentation here and here
There are quite a few ways to do this, but I think that's the simplest for your case.
You can :
1 - Store your csv in _data/foldername (eg : _data/members) see : Jekyll's data files
2 - Put all your datas in a new array with concat filter
{% comment %} ### Create an empty array{% endcomment %}
{% assign all-members = "" | split: "" %}
{% for part in site.data.members %}
{% assign all-members = all-members | concat: part[1] %}
{% endfor %}
3 - Output datas unsing the jsonify filter : {{ all-members | jsonify }}
An all in one, a members.json file can look like :
---
layout: null
---
{% assign all-members = "" | split: "" %}
{%- for part in site.data.members %}
{% assign all-members = all-members | concat: part[1] %}
{% endfor -%}
{{ all-members | jsonify }}
Thank you to both of you. I have gone the plugin route as this is the easiest and I may need to develop another plugin at some point so I may as well learn how to make one.

Jekyll: What is the default _data sorting criteria?

When iterating through an array of files in the _data folder, what is the default criteria for sorting the files?
At first I was expecting it to be sorted alphabetically, but after some testing I realized it was not. Still, I couldn't figure out what was the criteria being used to sort the files.
{%- for file in site.data.folder -%}
{{ file | inspect }}
<br />
<br />
{%- endfor -%}
From what I understood file is an array containing the filename as the first element and the data as the second element, so I'm not sure using sort with any property name would work. When I tried I had the error message:
Liquid Exception: no implicit conversion of String into Integer
When using sort with no arguments, I could return the files sorted by filename alphabetic order:
{%- assign files = site.data.folder | sort -%}
{%- for file in files -%}
{{ file | inspect }}
<br />
<br />
{%- endfor -%}
So my questions are:
What is the default sorting criteria for _data files?
Is sorting in relation to an object property possible? (I'm thinking the issue with that one is having an array and not the pure objects when you access site.data.folder)
Example:
After creating the default Jekyll page, I created the _data/folder directory, where I'd include 5 random .json files:
_data/folder/a.json
_data/folder/b.json
_data/folder/c.json
_data/folder/d.json
_data/folder/e.json
Each of them have the following content:
_data/folder/a.json:
{"name":"Mike"}
_data/folder/b.json:
{"id":"4343"}
_data/folder/c.json:
[{"age":"29"},{"job":"journalist"}]
_data/folder/d.json:
{"name":"John"}
_data/folder/e.json
{"haircolor":"green"}
With those files in place, I created a page named page.html on the root directory with:
---
---
<pre>{{ site.data.folder | inspect }}</pre>
<br />
<br />
{%- for file in site.data.folder -%}
<pre>{{ file | inspect }}</pre>
<br />
{%- endfor -%}
And the output of that page was:
{"e"=>{"haircolor"=>"green"}, "c"=>[{"age"=>"29"}, {"job"=>"journalist"}], "d"=>{"name"=>"John"}, "a"=>{"name"=>"Mike"}, "b"=>{"id"=>"4343"}}
["e", {"haircolor"=>"green"}]
["c", [{"age"=>"29"}, {"job"=>"journalist"}]]
["d", {"name"=>"John"}]
["a", {"name"=>"Mike"}]
["b", {"id"=>"4343"}]
The files were not ordered alphabetically, but instead in some apparently random order. I can get them in alphabetical order by using:
---
---
<pre>{{ site.data.folder | sort | inspect }}</pre>
<br />
<br />
{%- assign folder = site.data.folder | sort -%}
{%- for file in folder -%}
<pre>{{ file | inspect }}</pre>
<br />
{%- endfor -%}
Output:
[["a", {"name"=>"Mike"}], ["b", {"id"=>"4343"}], ["c", [{"age"=>"29"}, {"job"=>"journalist"}]], ["d", {"name"=>"John"}], ["e", {"haircolor"=>"green"}]]
["a", {"name"=>"Mike"}]
["b", {"id"=>"4343"}]
["c", [{"age"=>"29"}, {"job"=>"journalist"}]]
["d", {"name"=>"John"}]
["e", {"haircolor"=>"green"}]
But it's still unclear what is the ordering criteria on the call without sort.
Going from #ashmaroli's assumption that this was not a Jekyll's issue, I started making a little bit of research about file ordering and ran into the following resources:
File ordering behavior while using Dir on Ruby
Indeterministic File order using Dir
The link describes a counter intuitive behavior when loading multiple dependencies. If the order the files are loaded matter the shortcut below could result in they being loaded in a different order than the expected.
Dir[File.join(File.dirname(__FILE__), 'example/*.rb')].each{ |f| require f }
This is apparently due to the underlying glob system call according to the answer in the link.
Python glob ordering
How is Pythons glob.glob ordered?
In the SO question above, the user is asking why the returned glob file order in Python is different than the order on the output of ls -l. Even though the question is about Python and not Ruby, the underlying call to the OS is likely the same. The OS is not required to deliver the files in any order, so they should be sorted after the call.
The first answer states that if you run ls -U you get the unordered list of files, which matches the order I have here when I make a list of _data objects on Jekyll without sorting. So this is most likely the cause of the weird ordering: it's OS dependent.
Since Jekyll orders the _post files, I think it wouldn't be a major issue to order _data files by default as well, to avoid any confusion. But as it was stated before in the question itself, it can be easily done with the sort filter.

SaltStack jinja run command on master before pushing to minion

I have a simple task, which I'm trying to execute with Salt.
I want to dynamicly create a motd file for all my servers, which requires rendering ascii art at top, with the machines hostname.
I would like to make this render on the master, and then be pushed to the minion.
So far I have this simple file: /srv/salt/content/all/etc/update-motd.d/05-hostname
#!/bin/bash
cat << "EOF"
{{ salt.cmd.shell('figlet TestServer') }}
EOF
This file is then used in: /srv/salt/motd/init.sls
/etc/update-motd.d/05-hostname:
file.managed:
- source: salt://content/all/etc/update-motd.d/05-hostname
- template: jinja
If I try to run this, It'll will save the file with the output: /bin/sh: 1: figlet: not found, which I guess, is because the command is executed on the minion and not on the master.
sudo salt 'server' state.sls motd
I do realize, that I can make the saltmaster install figlet on all servers, but I think that would be a waste. The master already knows the hostname through grains, and it should therefore be a simple task, to generate this file on the master before pushing it.
Does anyone have any ideas for accomplishing this?
State jinja are rendered on the minion itself so there is no way the file.managed would work that way.
In order to render something on the master you need to use pillars.
So you would need to add a pillar on the master which looks something like this :
{% set host = grains['fqdn'] %}
{% set command = 'figlet ' + host %}
{% set output = salt.cmd.shell(command) %}
motd:
out: {{ output|yaml_encode }}
then point /srv/salt/content/all/etc/update-motd.d/05-hostname to the pillar.
#!/bin/bash
cat << "EOF"
{{ pillar['motd']['out'] }}
EOF

How can I test jinja2 templates in ansible?

Sometimes I need to test some jinja2 templates that I use in my ansible roles. What is the simplest way for doing this?
For example, I have a template (test.j2):
{% if users is defined and users %}
{% for user in users %}{{ user }}
{% endfor %}
{% endif %}
and vars (in group_vars/all):
---
users:
- Mike
- Smith
- Klara
- Alex
At this time exists 4 different variants:
1_Online (using https://cryptic-cliffs-32040.herokuapp.com/)Based on jinja2-live-parser code.
2_Interactive (using python and library jinja2, PyYaml)
import yaml
from jinja2 import Template
>>> template = Template("""
... {% if users is defined and users %}
... {% for user in users %}{{ user }}
... {% endfor %}
... {% endif %}
... """)
>>> values = yaml.load("""
... ---
... users:
... - Mike
... - Smith
... - Klara
... - Alex
... """)
>>> print "{}".format(template.render(values))
Mike
Smith
Klara
Alex
3_Ansible (using --check)
Create test playbook jinja2test.yml:
---
- hosts: 127.0.0.1
tasks:
- name: Test jinja2template
template: src=test.j2 dest=test.conf
and run it:
ansible-playbook jinja2test.yml --check --diff --connection=local
sample output:
PLAY [127.0.0.1] **************************************************************
GATHERING FACTS ***************************************************************
ok: [127.0.0.1]
TASK: [Test jinja2template] ***************************************************
--- before: test.conf
+++ after: /Users/user/ansible/test.j2
## -0,0 +1,4 ##
+Mike
+Smith
+Klara
+Alex
changed: [127.0.0.1]
PLAY RECAP ********************************************************************
127.0.0.1 : ok=2 changed=1 unreachable=0 failed=0
4_Ansible (using -m template) thanks for #artburkart
Make a file called test.txt.j2
{% if users is defined and users %}
{% for user in users %}
{{ user }}
{% endfor %}
{% endif %}
Call ansible like so:
ansible all -i "localhost," -c local -m template -a "src=test.txt.j2 dest=./test.txt" --extra-vars='{"users": ["Mike", "Smith", "Klara", "Alex"]}'
It will output a file called test.txt in the current directory, which will contain the output of the evaluated test.txt.j2 template.
I understand this doesn't directly use a vars file, but I think it's the simplest way to test a template without using any external dependencies. Also, I believe there are some differences between what the jinja2 library provides and what ansible provides, so using ansible directly circumvents any discrepancies. When the JSON that is fed to --extra-vars satisfies your needs, you can convert it to YAML and be on your way.
If you have a jinja2 template called test.j2 and a vars file located at group_vars/all.yml, then you can test the template with the following command:
ansible all -i localhost, -c local -m template -a "src=test.j2 dest=./test.txt" --extra-vars=#group_vars/all.yml
It will output a file called test.txt in the current directory, which will contain the output of the evaluated test.j2 template.
I think this is the simplest way to test a template without using any external dependencies. Also, there are differences between what the jinja2 library provides and what ansible provides, so using ansible directly circumvents any discrepancies. It's also possible to test ad-hoc variables without making an additional vars file by using JSON:
ansible all -i "localhost," -c local -m template -a "src=test.j2 dest=./test.txt" --extra-vars='{"users": ["Mike", "Smith", "Klara", "Alex"]}'
You can use the debug module
tasks:
- name: show templating results
debug:
msg: "{{ lookup('template', 'template-test.j2') }}"
Disclaimer - I am the author of this, but I put together JinjaFx (https://github.com/cmason3/jinjafx).
This is a Python based tool that allows you to pass Jinja2 templates with a YAML file for variables. I originally wrote it so it can pass CSV based data to generate group_vars and host_vars for our deployments, but it also allows easy testing of Jinja2 templates - there is an online version at https://jinjafx.io
I needed to verify that the template I had defined gave the right result for the server it was created for. (The template included the hostname as a variable and other per host defined variables.)
Neither of the above methods worked for me. The solution for me was to add
check_mode: yes
diff: yes
to the task executing the template command, this got me the difference between the generated file and the file actually on the server without changing the remote file.
For me it actually worked better than looking at the whole generated file, since the changes was the interesting part anyway.
It needs to log in on the remote machine, so a limited use-case.
Example of a complete command:
- name: diff server.properties
check_mode: yes
diff: yes
ansible.builtin.template:
src: "src.properties"
dest: "/opt/kafka/config/server.properties"