jinja做为一个模板语言,不仅使得saltstack中的sls文件能根据pillar和grains的值进行动态变化,同时因为引入了逻辑判断,使得原本十分呆板的普通配置文件也可以变得灵活起来。这一节我们通过实际操作来一起看看jinja如何玩耍。

我是T型人小付,一位坚持终身学习的互联网从业者。喜欢我的博客欢迎在csdn上关注我,如果有问题欢迎在底下的评论区交流,谢谢。

文章目录

  • 测试环境准备
  • jinja语法
    • {{ ... }}
    • {% ... %}
    • 格式优化
    • 模板的继承和复用
  • saltstack中的jinja
  • 总结

测试环境准备

这里还是坚持前几节一直用的官方vagrant演示环境,我们在这个基础上来搭建一个简单的jinja测试环境,用来实际感受下jinja的语法。

首先准备一个pillar文件用来存放jinja中会使用的变量,创建/srv/pillar/jinja_test.sls内容如下

name: james
age: 28

然后创建一个空文件/srv/salt/files/jinja_test.txt用来进行jinja模板的编辑

根据默认情况下的file_roots配置,/srv/salt/在file.managed中可以简单表示为salt://

最后创建一个state文件/srv/salt/jinja_test.sls来将上面的jinja模板同步到minion

/home/vagrant/jinja_test.txt:file.managed:- source: salt://files/jinja_test.txt- template: jinja

这样一个简单的测试环境就搭建好了,来将上面定义的pillar变量用jinja模板表示看看。新开一个文本文件jinja_test.txt,编辑如下

name: {{ pillar['name'] | capitalize() }}
age: {{ pillar['age'] }}

简单测试,语法不懂也没关系

然后在salt master上把pillar下发下去

root@saltmaster:/home/vagrant# salt * saltutil.refresh_pillar
minion1:True
minion2:True

之后对minion2去套用刚才的state文件

root@saltmaster:/srv/salt/files# salt 'minion2' state.apply jinja_test

成功了的话就可以在minion2上看到新建的/home/vagrant/jinja_test.txt文件,内容如下

vagrant@minion2:~$ cat jinja_test.txt
name: James
age: 28

如果成功显示了变量的内容。并且James开头是大写的J,那就表示测试环境配置成功了,可以开始进行下面具体的学习和实际操作了。后面我们用jinja语法去编辑jinja_test.txt文件然后下发到minion就可以看到实际效果了。

jinja语法

{{ … }}

在jinja中,两个大括号中间用来放会被打印为具体结果的表达式(Expressions)。常规的表达式包含以下几个部分:

  • python数据类型

例如整型数,浮点型数,字符串,列表,元组,字典等等

在测试环境的/srv/pillar/jinja_test.sls中增加下面两个变量

lucky number: [2,7,16]
pets: {'cat':'chouchou','dog':'haha'}

同时修改jinja模板jinja_test.txt添加下列内容

loved cat: {{ pillar['pets']['cat'] }}
3rd lucky number: {{ pillar['lucky number'][2] }}

然后对minion2更新文件

root@saltmaster:/home/vagrant# salt 'minion2' state.apply jinja_test

之后会发现目标文件多了两行

loved cat: chouchou
3rd lucky number: 16
  • 运算符号

例如常规的加减乘除 + - * /,以及余数%和求幂**

在上述的模板文件中添加

{{ 1+4 }}
{{ 2**4 }}
{{ 6%5 }}

目标文件会多出三行

5
16
1

python中可以用加号 + 去链接多个字符串,在jinja里面虽然也可以,但是推荐用专门的波浪线 ~ 去链接字符串

  • 比较符号和逻辑符号

就和python中一样,==/!=/>/>=/</<=/and/or/not。这里就不举例子了。需要注意的是这里只是纯粹的比较符号和逻辑符号,如果是和if语句一起使用的话就不能用两个大括号了,而要用到后面提到的百分号。

  • 一些特殊符号

in符号 - 和python中一样,判断元素是否在集合中。例如下面的表达式会返回True

{{ 'apple' in ['apple','banana','cherry'] }}

~符号 - 连接多个字符串。例如

{{ 'life'~' is '~'wonderful' }}

会返回life is wonderful

|符号 - 管道符号和shell里面一样,用来对前面的输出做进一步处理,在jinja中叫filter。可以查看官方的内建filter函数。举几个例子,例如

{{ 'debug the world' | capitalize() }}
{{ 'debug the world' | center(40) }}
{{ what | default('this') }}
{{ pillar['pets'] | dictsort(reverse=True) }}
{{ [1,2,3] | join('-') }}

会返回

Debug the worlddebug the world
this
[('dog', 'haha'), ('cat', 'chouchou')]
1-2-3
  • 执行salt命令

可以直接在jinja里面执行salt的命令行命令,中括号接命令,小括号接命令的参数,例如

{{ salt['pillar.get']('people','xiaofu') }}

相当于执行salt.states.pillar.get,查找key为people的pillar值,如果没查到就返回默认值xiaofu

因为可以有默认返回值,可以比{{ pillar['people'] }}能处理更复杂的情况,例如key不存在的时候,建议用命令的方式去获取pillar或者grains的值。

{% … %}

一个大括号和百分号一起,中间放条件选择和循环等等流程控制表达式。

  • for循环

在前面的/srv/pillar/jinja_test.sls中添加一个list如下

names: ['kobe','lebron','t-mac','wade']

然后修改jinja_test.txt添加下列内容,格式和python中的for很像,不过一定要有一个结尾来标志for循环的结束。for循环的中间用前面表示变量的方式去输出循环体的内容。

{%- for name in pillar['names'] %}
player: {{ name }}
{%- endfor %}

对minion2套用state文件,就可以看到目标文件多了下列内容

player: kobe
player: lebron
player: t-mac
player: wade

这里的百分号后面多了一个小短线,用来清除空格,可以查看后面的“格式优化”部分

针对上面pillar中定义的字典也是可以和python中一样进行循环

{%- for type, name in pillar['pets'].items() %}
type: {{ type }}, name: {{ name }}
{%- endfor %}

目标文件结果为

type: dog, name: haha
type: cat, name: chouchou

需要注意的是,字典是没有顺序的,可以用dictsort()的filter去预先进行排序处理一下。

同时,在for循环中,还有一些内建的变量和函数可以用来实现一些额外功能,见下面的这个官网提供的表格

Variable Description
loop.index The current iteration of the loop. (1 indexed)
loop.index0 The current iteration of the loop. (0 indexed)
loop.revindex The number of iterations from the end of the loop (1 indexed)
loop.revindex0 The number of iterations from the end of the loop (0 indexed)
loop.first True if first iteration.
loop.last True if last iteration.
loop.length The number of items in the sequence.
loop.cycle A helper function to cycle between a list of sequences. See the explanation below.
loop.depth Indicates how deep in a recursive loop the rendering currently is. Starts at level 1
loop.depth0 Indicates how deep in a recursive loop the rendering currently is. Starts at level 0
loop.previtem The item from the previous iteration of the loop. Undefined during the first iteration.
loop.nextitem The item from the following iteration of the loop. Undefined during the last iteration.
loop.changed(*val) True if previously called with a different value (or not called at all).

同样是前面第一个for循环的例子,如果改成下面这样

{%- for name in pillar['names'] %}
{%- if loop.index == 1 %}
player: {{ name | upper() }}
{%- else %}
player: {{ name }}
{%- endif %}
{%- endfor %}

这里的if语句下面马上会讲到

结果就变成了第一次循环的名字会大写

player: KOBE
player: lebron
player: t-mac
player: wade
  • if选择结构

正如上面所展示的那样,if选择结构如下

{% if xxx %}
xxx
{% else %}
xxx
{% endif %}

如果else后面还要继续嵌套if的话,可以用下面的结构

{% if xxx %}
xxx
{% elif xxx %}
xxx
{% else %}
xxx
{% endif %}

这里就不额外举例子了

  • Macro

类似于编程语言中的函数,jinja使用Macro去将一些会被经常使用的片段进行复用。

jinja_test.txt中添加下列macro的定义,其中macro的名字为info,带入4个参数,其中第4个参数是一个list。而且要注意这里是用短线去除了for循环元素前后的空格,然后利用波浪线~在内容的前面加了空格

{%- macro info(name, age, team, pets) -%}
We have a new player coming, his name is {{ name }}, and only {{ age }} years old. He belongs to team {{ team }}, and he loves
{%- for pet in pets -%}
{{ ' '~pet }}
{%- endfor %}
{%- endmacro -%}

然后在下面跟使用函数一样去使用macro,要注意这里用双大括号

{{ info('xiaofu', 18, 'suns', ['chouchou','haha']) }}

这样在目标文件中会输出下列内容

We have a new player coming, his name is xiaofu, and only 18 years old. He belongs to team suns, and he loves chouchou haha

需要注意的是这里是在同一个文件中进行定义和使用,如果macro的定义是在另一个模板文件中,还需要用后面讲到的import先进行导入才可以使用。

还需要注意,在两个大括号中再嵌套两个大括号的话,会报错。例如在macro中使用变量。

  • import

macro就跟函数一样,有过python编程经验的朋友应该知道python中在.py文件中定义的函数可以通过在别的文件中import来进行复用。jinja中也同样支持这个import方法。

首先在/srv/salt/files/form.txt中定义一个macro如下

{%- macro textarea(name, value='', rows=10, cols=40) -%}
<textarea name="{{ name }}" rows="{{ rows }}" cols="{{ cols }}">{{ value | e }}</textarea>
{%- endmacro %}

然后在/srv/salt/files/jinja_test.txt中修改如下内容

{% import "files/form.txt" as form %}
<p>
{{ form.textarea('comment') }}
</p>

最后下发到minion以后的效果如下

<p>
<textarea name="comment" rows="10" cols="40"></textarea>
</p>

除了可以import整个文件,也可以像python一样只是import单个函数

{% from "files/form.txt" import textarea as area %}
<p>
{{ area('comment') }}
</p>

最后的效果也是一样的。

  • 赋值

可以利用set标签加上等号去给单个变量赋值,例如

{% set exam = "Excellent" %}
{% set student = "xiaofu" %}
Your name: {{ student }}
Your exam result: {{ exam }}

和编程语言一样,需要注意的是变量的范围,区分全局变量和局部变量。

如果想把多行内容赋值给变量,可以用下面的方式

{%- set mylist -%}
<li><a href="/">Index</a></li>
<li><a href="/downloads">Downloads</a></li>
{%- endset %}

使用方式还是一样,例如

<ul>
{{ mylist }}
</ul>

就会输出下列结果

<ul>
<li><a href="/">Index</a></li>
<li><a href="/downloads">Downloads</a></li>
</ul>
  • extends和blocks

用于模板的继承和复用,见下面的模板的继承和复用部分

  • include

除了可以对模板进行继承来达到复用的效果,还可以比较简单粗暴地直接将现有的模板拿过来直接插入使用。使用的关键字就是{% include xxx %},然后被插入的模板内容就会直接出现在关键字所在的地方。

例如在/srv/salt/files/目录下有两个文件jinja_test.txtchild.html。编辑jinja_test.txt添加下列内容

{% include "files/child.html" %}

就可以把child.html的内容原封不动地复制到这里来。需要注意的是,这里的文件路径还是相对于salt://。

  • context

有一个比较难以理解的名词叫做context。默认情况下,include的模板会传递context,而import的模板不会传递context。

这个context跟变量的引用有一些关系,所以如果出现变量引用类的报错可以考虑修改下context的配置。

手动修改include以及import的context配置可以用下面的方法,这里用include来举例

{% from 'forms.html' import input with context %}
{% include 'header.html' without context %}

格式优化

  • 清除空格

默认情况下,for循环中会在输出的内容后自动加上一个空行,例如上面for循环的例子

{% for name in pillar['names'] %}
player: {{ name }}
{% endfor %}

这里没有加上去除空格的短横线,目标文件结果为

player: kobeplayer: lebronplayer: t-macplayer: wade

如果加上短横线,目标文件结果为

player: kobe
player: lebron
player: t-mac
player: wade

根据官方文档,在前后加短线的效果分别是去除前面和后面的空格

You can also strip whitespace in templates by hand. If you add a minus sign (-) to the start or end of a block (e.g. a For tag), a comment, or a variable expression, the whitespaces before or after that block will be removed

模板的继承和复用

前面讲到了macro关键字可以把一个片段类似于函数一样去复用,我们还可以更进一步,将一整个模板文件进行继承(Inheritance)。

继承是jinja非常重要的一个特性,尤其是在网页生成上,相信在github pages上自己用jekyll搭过网页的人应该都知道。可以首先搭建一个基础的骨骼出来,然后预留出一些区域供别的子模板去复写。在jinja里面,这些预留的区域用block关键字来表示。

用实际的例子看会比较容易理解。

首先创建/srv/salt/files/base.html文件内容如下

<!DOCTYPE html>
<html lang="en" dir="ltr"><head>{% block head %}<meta charset="utf-8"><link rel="stylesheet" href="/css/master.css"><title>{% block title %}{% endblock %} - My Webpage</title>{% endblock %}</head><body><div id="content">{% block content %}{% endblock %}</div><div id="footer">{% block footer %}&copy; Copyright 2019{% endblock %}</div></body>
</html>

可以看到上面用{% block xx %}{% endblock %}的方式去定义了四个区域,其中head区域还嵌套了title区域。这些区域都可以在别的模板中用对应的名字去覆盖,这个我们待会再来看。首先我们直接把这个文件用jinja的格式传递给minion看看是什么结果,编辑/srv/salt/jinja_test.sls

/home/vagrant/base.html:file.managed:- source: salt://files/base.html- template: jinja

然后下发到minion2
root@saltmaster:/home/vagrant# salt 'minion2' state.apply jinja_test
结果如下

<!DOCTYPE html>
<html lang="en" dir="ltr"><head><meta charset="utf-8"><link rel="stylesheet" href="/css/master.css"><title> - My Webpage</title></head><body><div id="content"></div><div id="footer">&copy; Copyright 2019</div></body>
</html>

然后我们新建一个/srv/salt/files/child.html去继承这个模板并且添加进自己的内容

{% extends "files/base.html" %}
{% block content %}
<h1>Index</h1>
<p class="important">Welcome to my awesome homepage</p>
{% endblock %}
{% block title %}
Index
{% endblock %}
{% block head %}
{{ super() }}
<style type="text/css">.important { color: #336699; }
</style>
{% endblock %}

这里的extends关键字用来声明继承关系,需要注意的是被继承的文件名是相对于salt://的路径,所以即使继承文件和被继承文件在同一个目录下也不能直接用文件名,会报错找不到文件。然后用和base.html中一样的block名字去进行覆盖。子文件中的block顺序不需要和父模板中一致,例如这里就是先覆盖的{% block content %}然后是{% block title %}最后是{% block head %}

然后试着把这个子文件下发到minion2

/home/vagrant/child.html:file.managed:- source: salt://files/child.html- template: jinja

最后效果如下

<!DOCTYPE html>
<html lang="en" dir="ltr"><head><meta charset="utf-8"><link rel="stylesheet" href="/css/master.css"><title>
Index- My Webpage</title><style type="text/css">.important { color: #336699; }
</style></head><body><div id="content"><h1>Index</h1>
<p class="important">Welcome to my awesome homepage</p></div><div id="footer">&copy; Copyright 2019</div></body>
</html>

虽然格式有点问题,但是可以看到子文件中的内容已经对应地填充到父模板的相应block了。值得一提的是{% block head %},如果想复用父模板中已经存在的内容,可以用{{ super() }}关键字去指明。

saltstack中的jinja

具体到saltstack里面,和普通的jinja区别就在于saltstack会传递给jinja很多内置的变量,例如pillar和grains,所以可以用这些变量的内容去进行逻辑判断以及模板服用。尤其是在state文件的生成上。

还是举个具体的例子来看。

有两个minion,分别是minion1minion2。现在要跑一个state文件,将两个不同的文件minion1.txtminion2.txt自动分配到两个minion的对应位置。两个位置用pillar下发下去。

首先在/srv/pillar/locations.sls中定义两个路径

location1: '/home/vagrant/'
location2: '/'

然后在/srv/pillar/top.sls中下发这个pillar到全部的minion

base:'*':- locations

下发

root@saltmaster:/srv/salt/files# salt '*' saltutil.refresh_pillar
minion1:True
minion2:True

然后创建/srv/salt/files/minion1.txt

this is for minion1

以及/srv/salt/files/minion2.txt

this is for minion2

之后就是重点了,创建一个state文件,自动根据minion的id去找到对应的文件和地址。创建/srv/salt/example.sls如下

{% if grains['id'] == 'minion1' %}
{% set location = pillar['location1']~'minion1.txt' %}
{% set source = 'minion1.txt' %}
{% else %}
{% set location = pillar['location2']~'minion2.txt' %}
{% set source = 'minion2.txt' %}
{% endif %}{{ location }}:file.managed:- source: salt://files/{{ source }}

首先根据grains的内容进行一个逻辑判断,并对locationsource这两个变量分别进行赋值。其中location的赋值还用到了pillar的内容。然后就是一个普通的文件复制state函数的写法了,只不过用到了上面定义的两个变量。

然后将这个state文件进行应用

root@saltmaster:/srv/salt/files# salt '*' state.apply example

然后就可以在两个minion的指定目录中分别看到指定的文件了

vagrant@minion1:~$ pwd
/home/vagrant
vagrant@minion1:~$ cat minion1.txt
this is for minion1
root@minion2:/# pwd
/
root@minion2:/# cat minion2.txt
this is for minion2

总结

jinja这种模板语言不仅在saltstack中使用,在很多网页搭建平台中也会用到。而且因为本身就是为python量身定做的模板引擎,以后利用Django后端对saltstack进行API二次开发的时候就会更加容易。如果对saltstack二次开发感兴趣的同学欢迎关注我,我们一起学习进步。

Saltstack入门到精通教程(五):Jinja详解相关推荐

  1. spark从入门到精通spark内存管理详解- 堆内堆外内存管理

    前言 Spark作为一个基于内存的分布式计算引擎,其内存管理模块在整个系统中扮演着非常重要的角色.理解Spark内存管理的基本原理,有助于更好地开发Spark应用程序和进行性能调优.本文将详细介绍两部 ...

  2. Spring Data JPA 从入门到精通~Naming命名策略详解及其实践

    Naming 命名策略详解及其实践 用 JPA 离不开 @Entity 实体,我都知道实体里面有字段映射,而字段映射的方法有两种: 显式命名:在映射配置时,设置的数据库表名.列名等,就是进行显式命名, ...

  3. Spring Data JPA 从入门到精通~Auditing及其事件详解

    Auditing 及其事件详解 Auditing 翻译过来是审计和审核,Spring 的优秀之处在于帮我们想到了很多繁琐事情的解决方案,我们在实际的业务系统中,针对一张表的操作大部分是需要记录谁什么时 ...

  4. Java快速入门到精通— Java break语句详解

    所有流行的编程语言中都有循环语句.JAVA 中采用的循环语句与C语言中的循环语句相似,主要有 while.do-while 和 for! 那么在某些时候需要在某种条件出现时强行终止循环,而不是等到循环 ...

  5. Modbus通信从入门到精通_2_Modbus TCP通信详解及仿真(搭建ModbusTCP仿真环境:创建虚拟PLC并进行ModbusTCP通讯;寄存器与PLC中映射关系;适合理解如何编写上位机)

    本篇将会以西门子PLC软件搭建ModbusTCP仿真环境,并通过仿真环境,介绍基础知识及模拟实际应用中写一个简单的通信读取PLC数据方法,并简介了编写上位机的方法. 文章目录 1. 搭建ModbusT ...

  6. Jmeter - 从入门到精通 - 环境搭建(详解教程)

    一.JMeter 介绍 Apache JMeter是100%纯JAVA桌面应用程序,被设计为用于测试客户端/服务端结构的软件(例如web应用程序).它可以用来测试静态和动态资源的性能,例如:静态文件, ...

  7. SpringBoot从入门到精通教程(二十九)- 微信企业支付集成(五分钟集成)

    需求背景 SpringBoot用法:微信企业支付集成(五分钟集成) 问题痛点 通过SpringBoot框架,集成服务端微信企业支付接口,做到下载即用(填写好相关微信支付后台相关Key信息),最快五分钟 ...

  8. SpringBoot从入门到精通教程(二十七)- @Valid注解用法详解+全局处理器Exception优雅处理参数验证用法

    问题痛点 用 Spring 框架写代码时,写接口类,相信大家对该类的写法非常熟悉.在写接口时要写效验请求参数逻辑,这时候我们会常用做法是写大量的 if 与 if else 类似这样的代码来做判断,如下 ...

  9. SpringBoot从入门到精通教程

    SpringBoot从入门到精通教程 一.来自ImportNew公众号的SpringBoot教程系列,可参考学习 SpringBoot (一) :入门篇--http://mp.weixin.qq.co ...

  10. python教程吾爱破解_2020年最新python入门到精通教程

    2020年最新python入门到精通教程 资源共享吧良心论坛,一直用心为每位会员服务,希望大家能学有所成,今天为大家分享一套2020年最新python入门到精通教程,如果你是0基础想学习python这 ...

最新文章

  1. FreeBSD5.3下安装Apache+PHP+MySQL+Tomcat
  2. Linux - 时间相关命令 - ntpdate, date, hwclock
  3. 前端学习(1405):多人管理25node.js—安装bcrypt出现错误的解决办法
  4. mybatis学习(36):动态sql-set
  5. CUDA bank 及bank conflict
  6. MySQL事件的使用
  7. SSH框架 openSessionInView的配置
  8. Java非对称加密开发(三)-代码及说明
  9. 显示当前服务器的ip,linux查看当前服务器ip地址 命令
  10. 威廉玛丽学院计算机专业,威廉玛丽学院CS排名2020年掌握的流程盘点
  11. DirectX9.0 DirectxSDK下载 DirectX9.03D游戏开发编程基础PDF+源代码
  12. oracle48108,​记一次oracle连接数暴涨hang分析经验
  13. Java 中 switch 的用法
  14. xftp和xshell有什么区别
  15. 小技巧使Windows Live Writer网络图片本地化
  16. 量子位智库报告:三分钟看懂ChatGPT | 附下载
  17. C语言数据浅谈之实型
  18. NOKIA 5110屏幕驱动
  19. 3dmax软件常用操作
  20. Browser-sync安装与使用

热门文章

  1. 调用远程摄像头进行人脸识别_工地如何实现安全帽检测/人脸识别?
  2. STM32-ADC模数转换
  3. 5-1MongoDB 实验——数据备份和恢复--edu上面的nosql题目
  4. Sql语句优化案例-两表关联查询
  5. 架构师的主要职责是什么?
  6. 京东联盟sdk php,京东联盟新增对接sdk,配置教程
  7. 北京云计算HCIE培训机构入门技术快速了解laaS、Paas和 SaaS的区别-ielab网络实验室
  8. 计算机中模板与母版的区别,ppt中母版模板主题版式之间的区别和联系?
  9. 图像的频谱图简单理解
  10. 5类6类7类网线对比_超详细的超五类、六类、七类网线的对比