Ansible——38.playbook jinja2模板

1. hello jinja2模板

cat temptest.yml
---
- hosts: test70
 remote_user: root
 gather_facts: no
 tasks:
 - yum:
     name: redis
     state: present
 - template:
     src: /testdir/ansible/redis.conf
     dest: /etc/redis.conf

模板功能可以灵活的生成配置文件,只需先选择一个文件作为模板文件,然后修改模板 文件中需要灵活生成的部分,使用变量进行替换(对应的变量必须提前定义好或者能够在运行时获取),模板文件中不需要灵活生成的部分保持不变即可,当需要为 各个目标主机生成配置文件时,只需调用template模块,template模块会在ansible控制机中对模板文件进行渲染,最终生成各个主机对应 的配置文件,然后拷贝到远程主机的指定位置中。
除了在playbook中能够使用template模块,在ad-hoc命令中也可以直接调用template模块,在ad-hoc命令中使用template模块比较方便测试模板文件的最终生成效果

 ansible test70 -m template -a "src=/testdir/ansible/redis.conf dest=/opt/redis.conf"

owner参数: 指定最终生成的文件拷贝到远程主机后的属主。
group参数: 指定最终生成的文件拷贝到远程主机后的属组。
mode参数: 指定最终生成的文件拷贝到远程主机后的权限,如果想将权限设置为"rw-r–r--",则可以使用mode=0644表示,如果想要在user对应的权限位上添加执行权限,则可以使用mode=u+x表示。
force参数: 当远程主机的目标路径中已经存在同名文件,并且与最终生成的文件内容不同时,是否强制覆盖,可选值有yes和no,默认值为yes,表示复盖,如果设置为no,则不会执行覆盖拷贝操作,远程主机中的文件保持不变。
backup参数: 当远程主机的目标路径中已经存在同名文件,并且与最终生成的文件内容不同时,是否对远程主机的文件进行备份,可选值有yes和no,当设置为yes时,会先备份远程主机中的文件,然后再将最终生成的文件拷贝到远程主机。

2. jinja2的语法

2.1 基本表达式

{{ }} :用来装载表达式,比如变量、运算表达式、比较表达式等。
{% %} :用来装载控制语句,比如 if 控制结构,for循环控制结构。
{# #} :用来装载注释,模板文件被渲染后,注释不会包含在最终生成的文件中。

cat test.j2
test jinja2 variable
test {{ testvar1 }} test
ansible test70 -m template -e "testvar1=teststr" -a "src=test.j2 dest=/opt/test"

除了变量,"{{ }}"中还可以包含一些表达式

cat test.j2
jinja2 test
{{ 1 == 1 }}
{{ 2 != 2 }}
{{ 2 > 1 }}
{{ 2 >= 1 }}
{{ 2 < 1 }}
{{ 2 <= 1 }}

生成文件内容如下:

cat test
jinja2 test
True
False
True
True
False
False

逻辑运算的相关示例

cat test.j2
jinja2 test
{{ (2 > 1) or (1 > 2) }}
{{ (2 > 1) and (1 > 2) }}
 
{{ not true }}
{{ not True }}
{{ not false }}
{{ not False }}

生成文件内容

cat test
jinja2 test
True
False
 
False
False
True
True

算数运算的相关示例
模板文件内容

cat test.j2
jinja2 test
{{ 3 + 2 }}
{{ 3 - 4 }}
{{ 3 * 5 }}
{{ 2 ** 3 }}
{{ 7 / 5 }}
{{ 7 // 5 }}
{{ 17 % 5 }}

生成文件内容

cat test
jinja2 test
5
-1
15
8
1.4
1
2

成员运算的相关示例

cat test.j2
jinja2 test
{{ 1 in [1,2,3,4] }}
{{ 1 not in [1,2,3,4] }}

生成文件内容

cat test
jinja2 test
True
False

jinja2本身就是基于python的模板引擎,所以,python的基础数据类型都可以包含在"{{ }}"中

cat test.j2
jinja2 test
### str
{{ 'testString' }}
{{ "testString" }}
### num
{{ 15 }}
{{ 18.8 }}
### list
{{ ['Aa','Bb','Cc','Dd'] }}
{{ ['Aa','Bb','Cc','Dd'].1 }}
{{ ['Aa','Bb','Cc','Dd'][1] }}
### tuple
{{ ('Aa','Bb','Cc','Dd') }}
{{ ('Aa','Bb','Cc','Dd').0 }}
{{ ('Aa','Bb','Cc','Dd')[0] }}
### dic
{{ {'name':'bob','age':18} }}
{{ {'name':'bob','age':18}.name }}
{{ {'name':'bob','age':18}['name'] }}
### Boolean
{{ True }}
{{ true }}
{{ False }}
{{ false }}

生成文件内容如下:

cat test
jinja2 test
### str
testString
testString
### num
15
18.8
### list
['Aa', 'Bb', 'Cc', 'Dd']
Bb
Bb
### tuple
('Aa', 'Bb', 'Cc', 'Dd')
Aa
Aa
### dic
{'age': 18, 'name': 'bob'}
bob
bob
### Boolean
True
True
False
False

字符串、数值、列表、元组、字典、布尔值等数据类型调用

jinja2 test
{{ teststr }}
{{ testnum }}
{{ testlist[1] }}
{{ testlist1[1] }}
{{ testdic['name'] }}
cat temptest.yml
---
- hosts: test70
 remote_user: root
 gather_facts: no
 vars:
   teststr: 'tstr'
   testnum: 18
   testlist: ['aA','bB','cC']
   testlist1:
   - AA
   - BB
   - CC
   testdic:
     name: bob
     age: 18
 tasks:
 - template:
     src: /testdir/ansible/test.j2
     dest: /opt/test

运行上例playbook以后,最终生成的文件如下

cat test
jinja2 test
tstr
18
bB
BB
bob

过滤器也可以直接在"{{ }}"中使用
模板文件内容

cat test.j2
jinja2 test
{{ 'abc' | upper }}

生成文件内容

cat test
jinja2 test
ABC

模板文件内容

cat test.j2
jinja2 test
{{ testvar1 is defined }}
{{ testvar1 is undefined }}
{{ '/opt' is exists }}
{{ '/opt' is file }}
{{ '/opt' is directory }}

执行命令时传入变量

ansible test70 -m template -e "testvar1=1 testvar2=2" -a "src=test.j2 dest=/opt/test"

生成文件内容

cat test
jinja2 test
True
False
True
False
True

模板文件内容如下

cat /testdir/ansible/test.j2
jinja2 test
{{ lookup('file','/testdir/testfile') }}
{{ lookup('env','PATH') }}
test jinja2

ansible主机中的testfile内容如下

cat /testdir/testfile
testfile in ansible
These are for testing purposes only

生成文件内容如下

cat test
jinja2 test
testfile in ansible
These are for testing purposes only
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin
test jinja2

注释信息
模板文件内容如下:

cat test.j2
jinja2 test
{#这是一行注释信息#}
jinja2 test
{#
这是多行注释信息,
模板被渲染以后,
最终的文件中不会包含这些信息
#}
jinja2 test

生成文件内容如下:

cat test
jinja2 test
jinja2 test
jinja2 test
2.2 条件语句
{% if 条件 %}
...
...
...
{% endif %}
cat test.j2
jinja2 test
 
{% if testnum > 3 %}
greater than 3
{% endif %}

不要使用ad-hoc命令调用template模块进行渲染,因为使用命令调用template模块时,无论传入的数据是哪种类型,都会被当做字符串进行处理,所以此处使用playbook渲染模板,以保证数据类型的正确。

cat temptest.yml
---
- hosts: test70
  remote_user: root
  gather_facts: no
  tasks:
  - template:
      src: /testdir/ansible/test.j2
      dest: /opt/test
    vars:
      testnum: 5	

最终生成的文件内容

cat /opt/test
jinja2 test
 
greater than 3
{% if 条件 %}
...
{% else %}
...
{% endif %}
{% if 条件一 %}
...
{% elif 条件二 %}
...
{% elif 条件N %}
...
{% endif %}
{% if 条件一 %}
...
{% elif 条件N %}
...
{% else %}
...
{% endif %}

if表达式,利用if表达式,可以实现类似三元运算的效果

cat test.j2
jinja2 test
{{ 'a' if 2>1 else 'b' }}

结果

cat /opt/test
jinja2 test
a
 <do something> if <something is true> else <do something else

在模板文件中定义变量

cat test.j2
jinja2 test
{% set teststr='abc' %}
{{ teststr }}
ansible test70 -m template -a "src=test.j2 dest=/opt/test"

最终生成的文件内容

cat /opt/test
jinja2 test
abc
2.3 循环语句
{% for 迭代变量 in 可迭代对象 %}
{{ 迭代变量 }}
{% endfor %}
cat test.j2
jinja2 test
{% for i in [3,1,7,8,2] %}
{{ i }}
{% endfor %}
ansible test70 -m template -a "src=test.j2 dest=/opt/test"

最终生成的文件内容

cat /opt/test
jinja2 test
3
1
7
8
2

每次循环后都会自动换行,如果不想要换行

cat test.j2
jinja2 test
{% for i in [3,1,7,8,2] -%}
{{ i }}
{%- endfor %}

在for的结束控制符"%}“之前添加了减号”-"
在endfor的开始控制符"{%“之后添加到了减号”-"
渲染上述模板,最终的生成效果

cat test
jinja2 test
31782

列表中的每一项都没有换行,而是连在了一起显示,如果觉得这样显示有些"拥挤",则可以稍微改进一下

jinja2 test
{% for i in [3,1,7,8,2] -%}
{{ i }}{{ ' ' }}
{%- endfor %}

最终生成的效果

cat test
jinja2 test
3 1 7 8 2

继续改进

cat test.j2
jinja2 test
{% for i in [3,1,7,8,2] -%}
{{ i~' ' }}
{%- endfor %}

jinja2中,波浪符"~"就是字符串连接符,它会把所有的操作数转换为字符串,并且连接它们

for除了能够循环操作列表,也能够循环操作字典
iteritems函数也可以替换成items函数,但是推荐使用iteritems函数

cat test.j2
jinja2 test
{% for key,val in {'name':'bob','age':18}.iteritems() %}
{{ key ~ ':' ~ val }}
{% endfor %}

最终生成内容

cat test
jinja2 test
age:18
name:bob

在使用for循环时,有一些内置的特殊变量可以使用,比如,如果我想要知道当前循环操作为整个循环的第几次操作,则可以借助"loop.index"特殊变量

cat test.j2
jinja2 test
{% for i in [3,1,7,8,2] %}
{{ i ~ '----' ~ loop.index }}
{% endfor %}

最终生成文件

cat test
jinja2 test
3----1
1----2
7----3
8----4
2----5

其他的内置变量
loop.index 当前循环操作为整个循环的第几次循环,序号从1开始
loop.index0 当前循环操作为整个循环的第几次循环,序号从0开始
loop.revindex 当前循环操作距离整个循环结束还有几次,序号到1结束
loop.revindex0 当前循环操作距离整个循环结束还有几次,序号到0结束
loop.first 当操作可迭代对象中的第一个元素时,此变量的值为true
loop.last 当操作可迭代对象中的最后一个元素时,此变量的值为true
loop.length 可迭代对象的长度
loop.depth 当使用递归的循环时,当前迭代所在的递归中的层级,层级序号从1开始
loop.depth0 当使用递归的循环时,当前迭代所在的递归中的层级,层级序号从0开始
loop.cycle() 这是一个辅助函数,通过这个函数可以在指定的一些值中进行轮询取值

对一段内容循环的生成指定的次数,则可以借助range函数完成,比如,循环3次,range函数可以指定起始数字、结束数字、步长等,默认的起始数字为0

{% for i in range(3) %}
something
...
{% endfor %}
{% for i in range(1,4,2) %}
  {{i}}
{% endfor %}

默认情况下,模板中的for循环不能像其他语言中的 for循环那样使用break或者continue跳出循环,但是可以在"for"循环中添加"if"过滤条件,以便符合条件时,循环才执行真正的操作

{% for i in [7,1,5,3,9] if i > 3 %}
  {{ i }}
{% endfor %}
{% for i in [7,1,5,3,9] %}
  {% if i>3 %}
    {{ i }}
  {%endif%}
{% endfor %}
{% for i in [7,1,5,3,9] if i>3 %}
{{ i ~'----'~ loop.index }}
{% endfor %}
 
{% for i in [7,1,5,3,9] %}
{% if i>3 %}
{{ i ~'----'~ loop.index}}
{% endif %}
{% endfor %}

最终生成的内容

cat test
7----1
5----2
9----3
 
7----1
5----3
9----5

for循环中使用了if内联表达式时,还可以与else控制语句结合使用

{% for i in [7,1,5,3,9] if i>10 %}
{{ i }}
{%else%}
no one is greater than 10
{% endfor %}

只有userlist列表为空时,才会渲染else块后的内容。

{% for u in userlist %}
  {{ u.name }}
{%else%}
  no one
{% endfor %}

for循环也支持递归操作
定义了一个字典变量,从字典中可以看出,bob的儿子是tom,tom的儿子是jerry,然后使用for循环操作了 这个字典,如前文所示,在操作字典时,使用了iteritems函数,在for循环的末尾,添加了recursive 修饰符,当for循环中有recursive时,表示这个循环是一个递归的循环,当需要在for循环中进行递归时,只要在需要进行递归的地方调用 loop函数即可,上例中的loop( value.iteritems() )即为调用递归的部分,由于value也是一个字典,所以需要使用iteritems函数进行处理

{% set dictionary={ 'name':'bob','son':{ 'name':'tom','son':{ 'name':'jerry' } } }  %}
 
{% for key,value in dictionary.iteritems() recursive %}
  {% if key == 'name' %}
    {% set fathername=value %}
  {% endif %}
 
  {% if key == 'son' %}
    {{ fathername ~"'s son is "~ value.name}}
    {{ loop( value.iteritems() ) }}
  {% endif %}
{% endfor %}

渲染最终效果

bob's son is tom
           
tom's son is jerry

辅助函数loop.cycle()

{% set userlist=['Naruto','Kakashi','Sasuke','Sakura','Lee','Gaara','Itachi']  %}
 
{% for u in userlist %}
{{ u ~'----'~ loop.cycle('team1','team2','team3')}}
{%endfor%}

最终生成内容

Naruto----team1
Kakashi----team2
Sasuke----team3
Sakura----team1
Lee----team2
Gaara----team3
Itachi----team1

默认情况下,模板中的for循环无法使用break和continue,不过jinja2支持一些扩展,如果在ansible中启用这些扩展,则可以让模板中的for循环支持break和continue,方法如下:
如果想要开启对应的扩展支持,需要修改ansible的配置文件/etc/ansible/ansible.cfg,默认情况下未启用jinja2 的扩展,如果想要启用jinja2扩展,则需要设置jinja2_extension选项,这个设置项默认情况下是注释的。
jinja2_extensions = jinja2.ext.do,jinja2.ext.i18n,jinja2.ext.loopcontrols
完成上述配置步骤即可在for循环中使用break和continue控制语句,与其他语言一样,break表示结束整个循环,continue表示结束当次循环

{% for i in [7,1,5,3,9] %}
  {% if loop.index is even %}
    {%continue%}
  {%endif%}
  {{ i ~'----'~ loop.index }}
{% endfor %}
{% for i in [7,1,5,3,9] %}
  {% if loop.index > 3 %}
    {%break%}
  {%endif%}
  {{i ~'---'~ loop.index}}
{% endfor %}

如果想要在jinja2中修改列表中的内容,则需要借助jinja2的另一个扩展,这个扩展的名字就是"do", 默认就有这个扩展,它的名字是jinja2.ext.do。

{% set testlist=[3,5] %}
 
{% for i in testlist  %}
  {{i}}
{% endfor %}
 
{%do testlist.append(7)%}
 
{% for i in testlist  %}
  {{i}}
{% endfor %}
2.4 转义
cat test.j2
{{  '{{' }}
{{  '}}' }}
{{ '{{ test string }}' }}
{{ '{% test string %}' }}
{{ '{# test string #}' }}

转义如果有较大的段落时,可以借助"{% raw %}"块,来实现刚才的需求

cat test.j2
{% raw %}
  {{ test }}
  {% test %}
  {# test #}
  {% if %}
  {% for %}
{% endraw %}

最终生成内容

cat test 
  {{ test }}
  {% test %}
  {# test #}
  {% if %}
  {% for %}

默认情况下,变量和表达式被包含在"{{ }}“中,控制语句被包含在”{% %}",其实,也可以在调用模板引擎时,手动的指定一些符号,这些符号可以替换默认的"{{ }}“和”{% %}",当在ansible中调用templdate模块时,可以使用variable_start_string参数指定一个符号,这个符号用于替 换"{{ }}“中的”{{",同时,可以使用variable_end_string参数指定一个符号,这个符号用于替换"{{ }}“中的”}}" 。

cat test.j2
{% set test='abc' %}
 
(( test ))
 
{{ test }}
{{ test1 }}
{{ 'test' }}
{{ 'test1' }}
ansible test70 -m template -a "src=test.j2 dest=/opt/test variable_start_string='((' variable_end_string='))'"

最终生成的文件

cat test

abc
 
{{ test }}
{{ test1 }}
{{ 'test' }}
{{ 'test1' }}

同理,可以使用block_start_string参数指定一个符号,这个符号用于替换"{% %}“中的”{% "
可以使用block_end_string参数指定一个符号,这个符号用于替换"{% %}“中的”%}"

2.5 宏

利用宏,可以方便快捷的重复的利用一段内容,并且把这段内容当做一个独立的逻辑单元,与其他语言中的函数一样,jinja2的宏也可以传入参数。
定义宏时需要使用"{% macro %}“开头,使用”{% endmacro %}“结束,上例中,定义了一个名为testfunc的宏,与大多数语言中的函数一样,宏的括号中可以传入参数,testfunc中的内容没有什么意义,只是用于示例宏的用法,”{% macro %}“和”{% endmacro %}"之间的内容只是用来定义宏,如果想要真正的使用宏,还需要引用它,最后一行就是在调用testfunc宏

cat test.j2
{% macro testfunc() %}
  test string
{% endmacro %}
 
{{ testfunc() }}

传参的宏

cat test.j2
{% set testvar1='teststr1' %}
{% set testvar2=2 %}
 
{% macro testfunc(tv1,tv2) %}
  test string
  {{tv1}}
  {{tv2}}
{% endmacro %}
 
{{ testfunc(testvar1,testvar2) }}

有一个很明显的问题,就是如果宏在定义的时候有对应的参数,在 调用宏时就必须传入对应的参数,否则就会报错,其实,还可以在定义宏时,为对应的参数指定一个默认值,当在调用宏时没有显式的指定对应的参数时,宏就 使用参数的默认值。没有默认值的参数必须在有默认值的参数之前,否则会出现错误。

{% macro testfunc(tv1=111) %}
  test string
  {{tv1}}
{% endmacro %}
 
{{ testfunc( ) }}
{{ testfunc(666) }}

渲染结果

  test string
  111
 
  test string
  666

在传入参数时,也可以显式的指明参数的名称。显式的指定参数名可以帮助灵活的传入参数。

{% macro testfunc(tv1,tv2=2,tv3=3) %}
  test string
  {{tv1}}
  {{tv2}}
  {{tv3}}
{% endmacro %}
 
{{ testfunc( 111,tv3='ccc' ) }}

渲染后内容

test string
111
2
ccc

在宏的内部,有三个默认的内置特殊变量可供使用,它们分别是varargs、kwargs、caller,在调用宏时,多传入几个额外的参数,这些额外的参数会作为一个元组保存在varargs变量上,可以通过获取varargs变量的值获取到额外传入的参数。

cat test.j2
{% macro testfunc(testarg1=1,testarg2=2) %}
  test string
  {{testarg1}}
  {{testarg2}}
  {{varargs}}
{% endmacro %}
 
{{ testfunc('a','b','c','d','e') }}

在调用宏时,传入了5个参数,而在宏里面,输出了内置变量varargs,最终渲染后的效果。

test string
  a
  b
  ('c', 'd', 'e')

如果宏没有定义任何参数,却传入了一些参数,那么这些所有传入的参数都是“多余”出的参数,也可以使用varargs变量处理这些参数

{% macro testfunc() %}
  test string
  {%for i in varargs%}
  {{i}}
  {%endfor%}
  {{ '--------' }}
{% endmacro %}
 
{{ testfunc() }}
{{ testfunc(1,2,3) }}

最终渲染效果

test string
    --------
 
  test string
    1
    2
    3
    --------

kwargs变量与varargs变量的作用很像,但是kwargs变量只是针对’关键字参数’而言的,而varargs变量是针对’非关键字参数’而言的

{% macro testfunc(tv1='tv1') %}
  test string
  {{varargs}}
  {{kwargs}}
{% endmacro %}
 
{{ testfunc('a',2,'test',testkeyvar='abc') }}

在定义宏时,定义了一个参数tv1,并且设置了默认值,在宏中,输出了varargs变量和kwargs变量,在调用宏时,多传入了3个参数,最后一个参数是一个带有参数名的关键字参数。多余的非关键字参数都会保存在varargs变量中,varargs变量的结构是一个元组,而多余的关键字参数都会保存在kwargs变量中,kwargs变量的结构是一个字典,kwargs变量实现的效果与Python的关键字参数效果类似。

  test string
  (2, 'test')
  {'testkeyvar': 'abc'}

与其说是caller变量,不如称其为caller函数或者caller方法,caller可以帮助将宏中的内容进行替换。定义了一个testfunc宏,在testfunc宏中,"{{caller()}}“部分可以被"其他内容"替换,但是 此刻,还没有调用testfunc宏,如果想要替换testfunc宏中的”{{caller()}}"部分,则需要在调用testfunc宏时,使 用"call语句块"进行调用。

{% macro testfunc() %}
  test string
  {{caller()}}
{% endmacro %}
{% macro testfunc() %}
  test string
  {{caller()}}
{% endmacro %}
 
{%call testfunc()%}
something~~~~~
something else~~~~~
{%endcall%}

使用了"{%call%}“语句块调用了testfunc宏,”{%call%}“和”{%endcall%}“之间的内容将会替换testfunc宏中的”{{caller()}}"部分,上例模板最终渲染效果

  test string
  something~~~~~
something else~~~~~

caller其实还能够帮助在一个宏中调用另一个宏。定义了两个宏,testfunc和testfunc1,将testfunc1传递到了testfunc中。

{% macro testfunc() %}
  test string
  {{caller()}}
{% endmacro %}
 
{% macro testfunc1() %}
  {% for i in range(3) %}
    {{i}}
  {% endfor %}
{% endmacro %}
 
{%call testfunc()%}
{{testfunc1()}}
{%endcall%}

caller()也可以接收参数,只要在call块中提前定义好,在caller中传入参数

{% macro testfunc() %}
  test string
  {{caller('somethingElse~~')}}
{% endmacro %}
 
{%call(testvar) testfunc()%}
something~~~~
{{testvar}}
{%endcall%}

call块中定义了testvar参数,call块中的内容使用了testvar参数,在testfunc宏中的调用caller时,传入了一个字符串作为参数,上例最终的渲染的效果。

  test string
  something~~~~
somethingElse~~

宏属性
name属性:宏的名称。
arguments属性:宏中定义的所有参数的参数名,这些参数名组成了一个元组存放在arguments中。
defaults属性:宏中定义的参数如果有默认值,这些默认值组成了一个元组存放在defaults中。
catch_varargs属性:如果宏中使用了varargs变量,此属性的值为true。
catch_kwargs属性: 如果宏中使用了kwargs变量,此属性的值为true。
caller属性:如果宏中使用了caller变量,此属性值为true。

cat test.j2
{% macro testfunc(tv1,tv2,tv3=3,tv4=4) %}
  test string
  {{tv1}}
  {{tv2}}
  {{tv3}}
  {{tv4}}
{% endmacro %}
 
  {{testfunc.name}}
  {{testfunc.arguments}}
  {{testfunc.defaults}}
  {{testfunc.catch_varargs}}
  {{testfunc.catch_kwargs}}
  {{testfunc.caller}}
 
{{'################################'}}
 
{% macro testfunc1(tv1='a',tv2='b') %}
  test string
  {{tv1}}
  {{tv2}}
  {{varargs}}
  {{kwargs}}
{% endmacro %}
 
  {{testfunc1.catch_varargs}}
  {{testfunc1.catch_kwargs}}
  {{testfunc1.caller}}
 
{{'################################'}}
 
{% macro testfunc2() %}
  test string
  {{caller()}}
{% endmacro %}
 
  {{testfunc2.caller}}

上例模板内容渲染后的结果

  testfunc
  ('tv1', 'tv2', 'tv3', 'tv4')
  (3, 4)
  False
  False
  False
 
################################
 
 
  True
  True
  False
 
################################
 
 
  True
2.6 包含
cat test.j2
test...................
test...................
{% include 'test1.j2' %}
 
test...................
 
# cat test1.j2
test1.j2 start
{% for i in range(3) %}
{{i}}
{% endfor %}
test1.j2 end

最终结果

test...................
test...................
test1.j2 start
0
1
2
test1.j2 end
test...................
cat test.j2
{% set varintest='var in test.j2' %}
test...................
test...................
{% include 'test1.j2' %}
 
test...................
 
cat test1.j2
test1.j2 start
{{ varintest }}
test1.j2 end

最终结果

test...................
test...................
test1.j2 start
var in test.j2
test1.j2 end
test...................

不想让被包含文件能够使用到外部文件中定义的变量,则可以使用"without context"显式的设置"include",当"include"中存在"without context"时,表示不导入对应的上下文

cat test.j2
{% set varintest='var in test.j2' %}
test...................
test...................
{% include 'test1.j2' without context %}
 
test...................
 

cat test1.j2
test1.j2 start
{{ varintest }}
test1.j2 end

会报错
可以显式的指定"with context",表示导入上下文

cat test.j2
test...................
test...................
{% include 'test1.j2' with context %}
 
test...................

默认情况下,即使不使用"with context","include"也会导入对应的上下文,所以,如下两种写法是等效的。

{% include 'test1.j2' %}
{% include 'test1.j2' with context %}

默认情况下,如果指定包含的文件不存在,则会报错

cat test.j2
test...................
test...................
{% include 'test1.j2' with context %}
 
test...................
{% include 'test2.j2' with context %}

在test.j2中指定包含了两个文件,test1.j2和test2.j2,但是,并没有编写test2.j2,所以,当渲染test.j2模板时,会报错
在指定包含的文件不存在时,自动忽略包含对应的文件,使用ignore missing标记

cat test.j2
test...................
test...................
{% include 'test1.j2' with context %}
 
test...................
{% include 'test2.j2' ignore missing with context %}
2.7 导入

无论是定义宏,还是调用宏,都是在同一个模板文件中完成的,通过import,可以实现在A文件中定义宏,在B文件中使用宏。

cat function_lib.j2
{% macro testfunc() %}
test function
{% for i in varargs %}
{{ i }}
{% endfor %}
{% endmacro %}
 
{% macro testfunc1(tv1=1) %}
{{tv1}}
{% endmacro %}
 
cat test.j2
{% import 'function_lib.j2' as funclib %}
something in test.j2
{{ funclib.testfunc(1,2,3) }}
 
something in test.j2
{{ funclib.testfunc1('aaaa') }}

将function_lib.j2文件中的宏导入到 funclib变量中
{% import ‘function_lib.j2’ as funclib %}
由于已经将"function_lib.j2"文件中的宏导入到了"funclib"变量中,所以当需要调用"function_lib.j2"文件中的testfunc宏时,直接使用{{ funclib.testfunc(1,2,3) }}

使用了 {% from ‘function_lib.j2’ import testfunc as tf, testfunc1 as tf1 %}导入了’function_lib.j2’文件中的两个宏
从’function_lib.j2’文件中将testfunc宏导入为tf宏
从’function_lib.j2’文件中将testfunc1宏导入为tf1宏
导入后,直接调用tf宏和tf1宏,即为调用’function_lib.j2’文件中对应的宏

cat function_lib.j2
{% macro testfunc() %}
test function
{% for i in varargs %}
{{ i }}
{% endfor %}
{% endmacro %}
 
{% macro testfunc1(tv1=111) %}
test function1
{{tv1}}
{% endmacro %}
 
 
cat test1.j2
{% from 'function_lib.j2' import testfunc as tf, testfunc1 as tf1  %}
something in test1.j2
{{ tf(1,2) }}
 
something in test1.j2
{{ tf1('a') }}

只导入testfunc1
{% from ‘function_lib.j2’ import testfunc1 as tf1 %}

两种import方法的不同
方法一:
{% import ‘function_lib.j2’ as funclib %}
一次性导入’function_lib.j2’ 文件中的所有宏,调用宏时使用对应的变量进行调用。
方法二:
{% from ‘function_lib.j2’ import testfunc1 as tf1 %}
导入’function_lib.j2’ 文件中指定的宏,调用宏时使用对应的新名称进行调用。

import和include不同,include默认会导入上下文环境,而import默认则不会,所以,如果想要让宏被import以后能够使用到对应的上下文环境,则需要显式的配置"with context"
cat function_lib.j2

{% macro testfunc1(tv1=111) %}
test function1
{{tv1}}
{{outvartest}}
{% endmacro %}
 
cat test.j2
{% set outvartest='00000000' %}
 
{% import 'function_lib.j2' as funclib with context%}
something in test.j2
{{ funclib.testfunc1() }}

宏中如果包含for循环并且for循环中使用了range()函数,那么在"import"宏时则必须显式的指定"with context",否则在ansible中渲染对应模板时,会出现报错

宏如果以一个或多个下划线开头,则表示这个宏为私有宏,这个宏不能被导入到其他文件中使用

cat func.j2
{% macro _test() %}
something in test macro
{% endmacro %}
 
{{_test()}}

上述宏不能被导入到其他文件,只能在当前文件中被调用。

2.8 继承

继承可以更加灵活的生成模板文件。
定义父模板

cat test.j2
something in test.j2...
something in test.j2...
{% block test %}
Some of the options that might be replaced
{% endblock %}
something in test.j2...
something in test.j2...

渲染后的结果

something in test.j2...
something in test.j2...
Some of the options that might be replaced
something in test.j2...
something in test.j2...

定义子模板

cat test1.j2
{% extends 'test.j2' %}
 
{% block test %}
aaaaaaaaaaaaaa
11111111111111
{% endblock %}

得到的结果
子模板中的test块中的内容覆盖了父模板中的test块的内容

something in test.j2...
something in test.j2...
aaaaaaaaaaaaaa
11111111111111
something in test.j2...
something in test.j2...

在父模板的块中不写任何内容,而是靠子模板去填充对应的内容

cat test.j2
something in test.j2...
something in test.j2...
{% block test %}
{% endblock %}
something in test.j2...
something in test.j2...
 
cat test1.j2
{% extends 'test.j2' %}
 
{% block test %}
aaaaaaaaaaaaaa
11111111111111
{% endblock %}

块中嵌套另一个块

something in test.j2...
{% block test %}
 
something in block test
{% block t1 %}
something in block t1
{% endblock %}
something in block test
 
{% endblock %}

无论test块还是t1块,都使用" {% endblock %}"作为结尾,虽然能够正常 解析,但是可读性比较差,可以在endblock中也加入对应的块名称以提高可读性。

something in test.j2...
{% block test %}
 
something in block test
{% block t1 %}
something in block t1
{% endblock t1 %}
something in block test
 
{% endblock test %}
something in test.j2...

在一个模板中多次的引用同一个块,则可以使用self特殊变量来引用模板自身的某个块

cat test.j2
something in test.j2...
 
{% block test %}
something in block test
something else in block test
{% endblock test %}
 
{{ self.test() }}
 
something in test.j2...

不完全覆盖父模板中的块,在父模板某个块的基础之上进行扩展,可以在子模板中使用super块来完成。

cat test.j2
something in test.j2...
 
{% block test %}
something in block test
something else in block test
{% endblock test %}
 
something in test.j2...
 
cat test1.j2
{% extends 'test.j2' %}
 
{% block test%}
aaaaaaaaaaaaaa
{{ super() }}
11111111111111
{% endblock test %}

在super块的末尾加入空白控制符"减号"就可以将自动换行去掉

{{ super() -}}

使用for循环去迭代一个块,但是在块中无法获取到for的循环变量。

cat test.j2
something in test.j2...
 
{%set testvar=123%}
{% block test %}
something in block test ---- {{testvar}}
{% endblock %}
 
{% for i in range(3) -%}
 
{% block test1 %}
something in block test1 ---- {{i}}
{% endblock %}
 
{%- endfor %}
 
something in test.j2...

test块和test1块,test块未使用for循环,test1块使用for循环进行处理,渲染上述模板,会报错"msg": “AnsibleUndefinedVariable: ‘i’ is undefined”
因为当test1块被for循环处理时,无法在块中获取到for的循环变量造成的,如果想要在上述情况中获取到for的循环变量,则可以在块中使用scoped修饰符。

cat test.j2
something in test.j2...
 
{%set testvar=123%}
{% block test %}
something in block test ---- {{testvar}}
{% endblock %}
 
{% for i in range(3) -%}
 
{% block test1 scoped %}
something in block test1 ---- {{i}}
{% endblock %}
 
{%- endfor %}
 
something in test.j2...

渲染后结果

something in test.j2...
 
something in block test ---- 123
 
something in block test1 ---- 0
something in block test1 ---- 1
something in block test1 ---- 2
 
something in test.j2...

在继承模板时,如果父模板在当前目录的子目录中,则可以使用如下方法继承对应的父模板。test1.j2为子模板,test.j2为父模板,父模板在子模板所在目录的子目录中,此时,可以使用’parent/test.j2’引用test.j2模板。

# tree
.
├── parent
│    └── test.j2
└── test1.j2
 
# cat test1.j2
{% extends 'parent/test.j2' %}
 
{% block test%}
{{ super() -}}
11111111111111
{% endblock test %}

————Blueicex 2020/3/30 16:23 [email protected]

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章