在Ansible中如何使用Jinja2模板
Jinja2是一个非常流行且功能强大的基于Python的模板引擎。
由于Ansible是用Python编写的,因此它成为大多数用户的默认选择,就像其他基于Python的配置管理系统(例如Fabric和SaltStack)一样。
Jinja这个名字起源于日语中的Temple一词,其发音与单词template相似。
Jinja2的一些重要功能包括:
它很快并且可以使用Python字节码及时编译
它具有可选的沙盒环境
很容易调试
它支持模板继承
变数
如我们所见,我们可以简单地使用'{{VARIABLE_NAME}}'语法打印变量内容。
如果只想打印一个数组的元素,可以使用'{{ARRAY_NAME ['KEY']}}''
,如果要打印对象的属性,可以使用'{{OBJECT_NAME. PROPERTY_NAME}}'。
例如,这里有一个剧本" jinja2_temp_1.yml",其中我使用" vars"在剧本中定义了一个变量:
-- - name: Data Manipulation hosts: localhost gather_facts: false vars: my_name: hynman Prasad tasks: - name: Print message debug: msg: "My name is {{ my_name }}"
让我们执行这个剧本:
[ansible@controller ~]$ansible-playbook jinja2_temp_1.yml PLAY [Data Manipulation] ** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** *** TASK [Print message] ** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** *** ok: [localhost] => { "msg": "My name is hynman Prasad" } PLAY RECAP ** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** ***** localhost : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
因此,使用{{my_name}}
访问变量,其中{{}}
被称为Jinja2语法。
这是访问剧本内任何变量的简单方法。
使用内置过滤器
过滤器与可以在变量上运行的小型函数或者方法相同。
有些过滤器不带参数,有些带可选参数,有些需要参数。
过滤器也可以链接在一起,其中一个过滤器操作的结果将被馈送到下一个过滤器和下一个过滤器中。
Jinja2带有许多内置过滤器
过滤器通过管道符号(|
)应用于变量,其后是过滤器的名称,然后是括号内该过滤器的所有参数。
变量名称和管道符号之间可以有空格,管道符号和过滤器名称之间也可以有空格。
语法过滤器
有时,我们可能希望稍微更改字符串的样式,而无需为其编写特定的代码,例如,我们可能希望将一些文本大写。
为此,我们可以使用Jinja2的过滤器之一,例如:'{{VARIABLE_NAME |大写}}'
。
Jinja2有许多可用的过滤器,可以从Jinja官方收集这些过滤器。
在下一个剧本" jinja2_temp_2.yml"中,我们将通过一些示例使用过滤器执行数据操作:
-- - name: Data Manipulation hosts: localhost gather_facts: false vars: my_name: hynman Prasad tasks: - name: Print message debug: msg: - "My name is {{ my_name }}" - "My name is {{ my_name | lower }}" - "My name is {{ my_name | upper }}" - "My name is {{ my_name | capitalize }}" - "My name is {{ my_name | title }}"
其中我们使用不同的过滤器打印相同的消息。
过滤器将仅应用于我们在vars下定义的变量,即my_name。
让我们执行这个剧本:
[ansible@controller ~]$ansible-playbook jinja2_temp_2.yml PLAY [Data Manipulation] ** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** *** TASK [Print message] ** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** *** ok: [localhost] => { "msg": [ "My name is hynman Prasad", "My name is hynman prasad", "My name is hynman PRASAD", "My name is hynman prasad", "My name is hynman Prasad" ] } PLAY RECAP ** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** ***** localhost : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
我们可以观察到基于所应用的过滤器处理hynman Prasad值的输出差异。
默认过滤器
默认过滤器是为未定义的变量提供默认值的一种方式,这将防止Ansible生成错误。
这是复杂的if语句的简写形式,它在尝试使用变量之前先检查是否已定义变量,并带有else子句以提供不同的值。
在这个示例剧本" jinja2_temp_3.yml"中,我定义了" first_name"变量,但未定义" last_name"
-- - name: Data Manipulation hosts: localhost gather_facts: false vars: first_name: hynman tasks: - name: Print message debug: msg: - "My name is {{ first_name }} {{ last_name }}"
因此,当我执行此剧本时,出现以下错误:
TASK [Print message] ** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** *** fatal: [localhost]: FAILED! => {"msg": "The task includes an option with an undefined variable. The error was: 'last_name' is undefined\n\nThe error appears to be in '/home/ansible/jinja2_temp_3.yml': line 8, column 6, but may\nbe elsewhere in the file depending on the exact syntax problem.\n\nThe offending line appears to be:\n\n tasks:\n - name: Print message\n ^ here\n"}
几乎可以预料到此错误,因为它说任务包括带有未定义变量的选项
现在我们可以通过使用变量定义一个"默认"过滤器来处理这种情况。
我已经更新了剧本,并添加了带有last_name
的默认过滤器,因此,如果未定义此变量,则将使用默认值:
-- - name: Data Manipulation hosts: localhost gather_facts: false vars: first_name: hynman tasks: - name: Print message debug: msg: - "My name is {{ first_name }} {{ last_name | default('Prasad') }}"
现在,如果我们执行此剧本,我们将为last_name
变量获得适当的值:
[ansible@controller ~]$ansible-playbook jinja2_temp_3.yml PLAY [Data Manipulation] ** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** *** TASK [Print message] ** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** *** ok: [localhost] => { "msg": [ "My name is hynman Prasad" ] } PLAY RECAP ** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** ***** localhost : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
列出并设定
我们可以使用内置的过滤器来遍历List的各个值并执行诸如查找List中的最高编号或者最低编号之类的操作。
让我们举一些例子:
在此示例剧本中,我们在my_vars下定义的列表上执行了一系列操作:
-- - name: Data Manipulation hosts: localhost gather_facts: false vars: my_list: [1,2,3,4,5,6,5,3,7,1,9] tasks: - name: List and Set debug: msg: - "The highest no {{ my_list | max }}" - "The lowest no is {{ my_list | min }}" - "Print only unique values {{ my_list | unique }}" - "Print random no {{ my_list | random }}" - "Join the values of list {{ my_list | join('-') }}"
执行此播放列表后,让我们检查输出:
TASK [List and Set] ** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** ok: [localhost] => { "msg": [ "The highest no 9", "The lowest no is 1", "Print only unique values [1, 2, 3, 4, 5, 6, 7, 9]", "Print random no 1", "Join the values of list 1-2-3-4-5-6-5-3-7-1-9" ] }
其中我们使用Jinja2过滤器迭代" my_list"的各个值,然后
使用
max
只打印最高的数字使用
min
只打印最低的数字使用
unique
仅打印唯一值使用
random
从列表中打印一个随机值将列表值与连字符(
-
)连接起来并打印输出。
过滤器处理路径名
配置管理和编排经常引用路径名,但是通常只需要路径的一部分。
Ansible提供了一些过滤器来。
在这个示例剧本" jinja2_temp_5.yml"中,我在Linux和Windows PATH上使用了多个过滤器:
-- - name: Data Manipulation hosts: localhost gather_facts: false vars: path1: "/opt/custom/data/bin/script.sh" path2: 'C:\Users\deeprasa\PycharmProjects\elasticsearch\test.log' path3: "~/jinja2_temp_5.yml" tasks: - name: filters to work on pathnames debug: msg: - "Linux Path: {{ path1 | dirname }}" - "Windows Path: {{ path2 | win_dirname }}" - "Linux script name: {{ path1 | basename }}" - "Split the path: {{ path2 | win_splitdrive }}" - "Windows Drive: {{ path2 | win_splitdrive | first }}" - "Windows File name: {{ path2 | win_splitdrive | last }}" - "Show Full path: {{ path3 | expanduser }}"
剧本的输出:
[ansible@controller ~]$ansible-playbook jinja2_temp_5.yml PLAY [Data Manipulation] ** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** *** TASK [filters to work on pathnames] ** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** ok: [localhost] => { "msg": [ "Linux Path: /opt/custom/data/bin", "script.sh", "Split the path: ('C:', '\\Users\\deeprasa\\PycharmProjects\\elasticsearch\\test.log')", "Windows Drive: C:", "Windows File name: \Users\deeprasa\PycharmProjects\elasticsearch\test.log", "Show Full path: /home/ansible/jinja2_temp_5.yml" ] } PLAY RECAP ** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** ***** localhost : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
筛选日期和时间
我们有不同的过滤器来处理日期和时间。
-- - name: Data Manipulation hosts: localhost gather_facts: false vars: mydate1: "2017-08-14 20:00:00" mydate2: "2016-08-15 21:01:40" tasks: - name: Date and time filters debug: msg: - "Today's date: {{ '%d-%m-%Y' | strftime }}" - "Today's date and time: {{ '%d-%m-%Y %H:%M:%S' | strftime }}" - "Print seconds since {{ mydate1 }}: {{ ((mydate2 | to_datetime) - (mydate1 | to_datetime)).seconds }}" - "Print days since {{ mydate2 }}: {{ ((mydate2 | to_datetime) - (mydate1 | to_datetime)).days }}"
在这个剧本" jinja2_temp_6.yml"中,我们仅在前两个调试消息中打印日期和时间,而在后两个中,我们将自提供的日期起经过的时间转换为秒和天。
让我们执行这个剧本:
[ansible@controller ~]$ansible-playbook jinja2_temp_6.yml PLAY [Data Manipulation] ** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** *** TASK [Date and time filters] ** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** *** ok: [localhost] => { "msg": [ "Today's date: 24-09-2017", "Today's date and time: 24-09-2017 06:53:45", "Print seconds since 2017-08-14 20:00:00: 3700", "Print days since 2016-08-15 21:01:40: -730" ] } PLAY RECAP ** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** ***** localhost : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
使用Jinja2模板配置VSFTPD
现在我们对Jinja2过滤器和语法有所了解,我们将使用Jinja2模板,因此在我们的受管节点之一上配置vsftpd
。
我将创建" lab2"项目目录,在此示例中将使用该目录。
这里的Project只是一个新目录,其中包含剧本需要的所有内容,例如" ansible.cfg",列表等。
[ansible@controller ~]$mkdir lab2 [ansible@controller ~]$cd lab2/
接下来,将" ansible.cfg"从默认位置复制到项目目录
[ansible@controller lab2]$cp /etc/ansible/ansible.cfg .
我们将使用单个受管节点条目创建自己的列表文件,因为在此示例中,我不需要多个受管节点:
[ansible@controller lab2]$cat inventory server2
创建一个模板目录并其中导航:
[ansible@controller lab2]$mkdir templates [ansible@controller lab2]$cd templates/
我们创建了一个Jinja2模板文件,其中包含使用vsftpd配置FTP服务器所需的内容。
我还使用了一些事实来从受管节点获取IPv4地址,并将其放置在" vsftpd.conf"中,仅供参考。
[ansible@controller templates]$cat vsftpd.j2 anonymous_enable={{ anonymous_enable }} local_enable={{ local_enable }} write_enable={{ write_enable }} anon_upload_enable={{ anon_upload_enable }} dirmessage_enable=YES xferlog_enable=YES connect_from_port_20=YES pam_service_name=vsftpd userlist_enable=YES # MY IP Address={{ ansible_facts['default_ipv4']['address'] }}
现在我们的Jinja2模板已经准备好了,我们将在lab2
目录中创建我们的剧本configure_vsftpd.yml
,该目录将在server2
上安装并配置vsftpd
。
因此,该剧本将执行2个任务。
[ansible@controller lab2]$cat configure_vsftpd.yml -- - name: Install and Configure vSFTPD hosts: server2 become: yes vars: anonymous_enable: yes local_enable: yes write_enable: yes anon_upload_enable: yes tasks: - name: install vsftp yum: name: vsftpd - name: use Jinja2 template to configure vsftpd template: src: vsftpd.j2 dest: /etc/vsftpd/vsftpd.conf
现在,我们将执行该剧本:
[ansible@controller lab2]$ansible-playbook configure_vsftpd.yml PLAY [Install and Configure vSFTPD] ** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** TASK [Gathering Facts] ** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** ***** ok: [server2] TASK [install vsftp] ** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** *** changed: [server2] TASK [use Jinja2 template to configure vsftpd] ** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** ***** changed: [server2] PLAY RECAP ** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** ***** server2 : ok=3 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
因此,剧本已成功执行,并且将其更改为= 2,这意味着我们的两项任务均已完成。
让我们验证来自server2的vsftpd.conf内容
[ansible@server2 ~]$sudo cat /etc/vsftpd/vsftpd.conf anonymous_enable=True local_enable=True write_enable=True anon_upload_enable=True dirmessage_enable=YES xferlog_enable=YES connect_from_port_20=YES pam_service_name=vsftpd userlist_enable=YES # MY IP Address=172.31.23.18