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.
Related
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
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"
I am working on a Jekyll site on and I want to be able to have a page for each person in the group. I know I can use a collections to generate pages, if the files in the collection are markdown. I want to be able to have yaml files in the collection, then generate pages after passing each yaml file to a template.
People files might look like this:
# person-1.yaml
name: thingy m. bob
position: coffee fetcher
bio: no bio needed
# person-2.yaml
name: mars e. pan
position: head honcho
bio: expert in everything
Then a template file like this (people-template.md):
# {{ page.name }} - {{ page.position }}
{{ page.bio }}
And the output would be individual file under /people/, i.e, /people/person-1, /people/person-2, which are formatted as in the template, but using the .yaml files.
I am using GitHub pages, so I don't want to have to use any plugins which that doesn't support.
I have implemented something similar ... this is the setup I created:
- _Layouts
- person.html
...
people
- index.md (list of people - see code below)
- _posts
- 2015-01-01-person-one.md (ordering defined by date which is thrown away)
- 2015-01-02-person-two.md
- 2015-01-03-person-three.md
...
Then for a list of people you can use something like:
<ul>
{% for person in site.categories.people %}
<li></li>
{% endfor %}
</ul>
with each person being in the form
---
name: "thingy m. bob"
# using quotes to avoid issues with non-alpha characters
position: "coffee fetcher"
bio: "no bio needed"
layout: person
---
any markdown if you want more of a description
I hope that has given you something to start with ... I think that putting the _posts folder under the people folder will automatically set the category to people. If I am wrong, just add category: people to the yaml.
You can set the pattern for the post urls in _config.yaml if you want to remove the date part.
Good luck
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 %}
I'm creating a formula/state file that adds an iptables rule:
ipt_allow:
iptables.append:
- table: filter
- chain: INPUT
- jump: ACCEPT
- match: state
- connstate: 'NEW,ESTABLISHED'
- dport: 6666
- proto: tcp
- source: 'ip1, ip2, ip3, ...'
I don't want to hard code the IP addresses in source. ip1, ip2, and ip3 are the IP addresses of minions that match the grain role:role1. That way, if I ever add more minions with the grain role1 down the road, they'll get added to the iptables rule once I rerun this state file. How do I do this?
Got it. This can be done using Salt mine functions.
/srv/pillar/mines.sls:
mine_functions:
network.ip_addrs:
- eth1
This makes the network.ip_addrs available to the master and minions, specifically the eth1 interface.
/srv/pillar/top.sls:
base:
'*':
- mines
This applies the pillar (hence the mine function) to all the minions.
Now you can use the mine.get function in your state files to call that mine function. This is what I used in my case:
{% for server, addrs in salt['mine.get']('role:role1', 'network.ip_addrs', expr_form='grain').items() %}
ipt_allow_{{ server }}:
iptables.append:
- table: filter
- chain: INPUT
- jump: ACCEPT
- match: state
- connstate: 'NEW,ESTABLISHED'
- dport: 6666
- proto: tcp
- source: {{ addrs[0] }}
{% endfor %}
Where:
salt['mine.get'] calls the mine.get function,
('role:role1', 'network.ip_addrs', expr_form='grain').items() tells the function to match minions that have role1 as the value of role which is a grain (expr_form='grain') and get its network.ip_addrs.
The output is the matched minion id (I believe) and the result of network.ip_addrs, so we store that in server and addrs.
The for loop loops goes around creating a new ID every time using the value in {{ server }} and substituting the IP address in {{ addrs[0] }}.
Links that helped:
http://docs.saltstack.com/en/latest/topics/mine/index.html
http://docs.saltstack.com/en/latest/ref/modules/all/salt.modules.mine.html
Using the output of salt to be used as input for an SLS state or pillar?