mirror of
https://github.com/valitydev/salt.git
synced 2024-11-06 16:45:27 +00:00
164 lines
5.0 KiB
ReStructuredText
164 lines
5.0 KiB
ReStructuredText
.. _tutorial-jinja_to_execution-module:
|
|
|
|
=================================================
|
|
How to Convert Jinja Logic to an Execution Module
|
|
=================================================
|
|
|
|
.. versionadded: 2016.???
|
|
|
|
.. note:
|
|
|
|
This tutorial assumes a basic knowledge of Salt states and specifically
|
|
experience using the `maps.jinja` idiom.
|
|
|
|
This tutorial was written by a salt user who was told "if your maps.jinja
|
|
is too complicated, write an execution module!". If you are experiencing
|
|
over-complicated jinja, read on.
|
|
|
|
The Problem: Jinja Gone Wild
|
|
----------------------------
|
|
|
|
It is often said in the Salt community that "Jinja is not a Programming Language".
|
|
There's an even older saying known as Maslow's hammer.
|
|
It goes something like
|
|
"if all you have is a hammer, everything looks like a nail".
|
|
Jinja is a reliable hammer, and so is the `maps.jinja` idiom.
|
|
Unfortunately, it can lead to code that looks like the following.
|
|
|
|
.. code-block:: jinja
|
|
|
|
# storage/maps.yaml
|
|
|
|
{% import_yaml 'storage/defaults.yaml' as default_settings %}
|
|
{% set storage = default_settings.storage %}
|
|
{% do storage.update(salt['grains.filter_by']({
|
|
'Debian': {
|
|
},
|
|
'RedHat': {
|
|
}
|
|
}, merge=salt['pillar.get']('storage:lookup'))) %}
|
|
|
|
{% if 'VirtualBox' == grains.get('virtual', None) or 'oracle' == grains.get('virtual', None) %}
|
|
{% do storage.update({'depot_ip': '192.168.33.81', 'server_ip': '192.168.33.51'}) %}
|
|
{% else %}
|
|
{% set colo = pillar.get('inventory', {}).get('colo', 'Unknown') %}
|
|
{% set servers_list = pillar.get('storage_servers', {}).get(colo, [storage.depot_ip, ]) %}
|
|
{% if opts.id.startswith('foo') %}
|
|
{% set modulus = servers_list | count %}
|
|
{% set integer_id = opts.id | replace('foo', '') | int %}
|
|
{% set server_index = integer_id % modulus %}
|
|
{% else %}
|
|
{% set server_index = 0 %}
|
|
{% endif %}
|
|
{% do storage.update({'server_ip': servers_list[server_index]}) %}
|
|
{% endif %}
|
|
|
|
{% for network, _ in salt.pillar.get('inventory:networks', {}) | dictsort %}
|
|
{% do storage.ipsets.hash_net.foo_networks.append(network) %}
|
|
{% endfor %}
|
|
|
|
This is an example from the author's salt formulae demonstrating misuse of jinja.
|
|
Aside from being difficult to read and maintain,
|
|
accessing the logic it contains from a non-jinja renderer
|
|
while probably possible is a significant barrier!
|
|
|
|
Refactor
|
|
--------
|
|
|
|
The first step is to reduce the maps.jinja file to something reasonable.
|
|
This gives us an idea of what the module we are writing needs to do.
|
|
There is a lot of logic around selecting a storage server ip.
|
|
Let's move that to an execution module.
|
|
|
|
.. code-block:: jinja
|
|
|
|
# storage/maps.yaml
|
|
|
|
{% import_yaml 'storage/defaults.yaml' as default_settings %}
|
|
{% set storage = default_settings.storage %}
|
|
{% do storage.update(salt['grains.filter_by']({
|
|
'Debian': {
|
|
},
|
|
'RedHat': {
|
|
}
|
|
}, merge=salt['pillar.get']('storage:lookup'))) %}
|
|
|
|
{% if 'VirtualBox' == grains.get('virtual', None) or 'oracle' == grains.get('virtual', None) %}
|
|
{% do storage.update({'depot_ip': '192.168.33.81'}) %}
|
|
{% endif %}
|
|
|
|
{% do storage.update({'server_ip': salt['storage.ip']()}) %}
|
|
|
|
{% for network, _ in salt.pillar.get('inventory:networks', {}) | dictsort %}
|
|
{% do storage.ipsets.hash_net.af_networks.append(network) %}
|
|
{% endfor %}
|
|
|
|
And then, write the module.
|
|
Note how the module encapsulates all of the logic around finding the storage server IP.
|
|
|
|
.. code-block:: python
|
|
|
|
# _modules/storage.py
|
|
#!python
|
|
|
|
'''
|
|
Functions related to storage servers.
|
|
'''
|
|
|
|
import re
|
|
|
|
|
|
def ips():
|
|
'''
|
|
Provide a list of all local storage server IPs.
|
|
|
|
CLI Example::
|
|
|
|
salt \* storage.ips
|
|
'''
|
|
|
|
if __grains__.get('virtual', None) in ['VirtualBox', 'oracle']:
|
|
return ['192.168.33.51', ]
|
|
|
|
colo = __pillar__.get('inventory', {}).get('colo', 'Unknown')
|
|
return __pillar__.get('storage_servers', {}).get(colo, ['unknown', ])
|
|
|
|
|
|
def ip():
|
|
'''
|
|
Select and return a local storage server IP.
|
|
|
|
This loadbalances across storage servers by using the modulus of the client's id number.
|
|
|
|
:maintainer: Andrew Hammond <ahammond@anchorfree.com>
|
|
:maturity: new
|
|
:depends: None
|
|
:platform: all
|
|
|
|
CLI Example::
|
|
|
|
salt \* storage.ip
|
|
|
|
'''
|
|
|
|
numerical_suffix = re.compile(r'^.*(\d+)$')
|
|
servers_list = ips()
|
|
|
|
m = numerical_suffix.match(__grains__['id'])
|
|
if m:
|
|
modulus = len(servers_list)
|
|
server_number = int(m.group(1))
|
|
server_index = server_number % modulus
|
|
else:
|
|
server_index = 0
|
|
|
|
return servers_list[server_index]
|
|
|
|
Conclusion
|
|
----------
|
|
|
|
That was... surprisingly straight-forward.
|
|
Now the logic is available in every renderer, instead of just Jinja.
|
|
Best of all, it can be maintained in Python,
|
|
which is a whole lot easier than Jinja.
|