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]

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