ansible Jinja2 流程控制

Jinja2 在 Ansible 中的模板渲染功能非常强大,支持条件判断 (if)、循环 (for)、过滤器 (filters)、宏 (macros)、变量 (set)、以及包含 (include) 等流程控制机制。以下是更详细的介绍,并配有示例和参数解析。

1. 条件判断 (if 语句)

Jinja2 支持 if 语句进行逻辑判断,根据变量的值动态控制模板内容。

基本语法

{% if condition %}
    条件为 True 时执行的代码
{% elif other_condition %}
    另一个条件为 True 时执行的代码
{% else %}
    所有条件都不满足时执行的代码
{% endif %}

示例

1.1 判断某个变量的值

{% if ansible_os_family == "Debian" %}
    apt update && apt upgrade -y
{% elif ansible_os_family == "RedHat" %}
    yum update -y
{% else %}
    echo "Unsupported OS"
{% endif %}
  • ansible_os_family 是一个 Ansible 变量,它的值可以是 "Debian""RedHat" 等。
  • 这里根据不同的操作系统执行不同的命令。

1.2 检查变量是否已定义

{% if my_variable is defined %}
    {{ my_variable }}
{% else %}
    "my_variable 未定义"
{% endif %}

is defined 判断变量是否存在,防止 UndefinedError

示例

[root@master-1 nginx]# cat vars.yaml
nginx_version: "1.22.1"
nginx_src_dir: "/tmp/nginx/"
nginx_install_dir: "/usr/local/nginx"
#server_name: localhost
server_name: 127.0.0.1
#listen_port: 80
 server {
    {% if listen_port is defined %}
      listen    {{ listen_port }};
    {% else %}
      listen 80;   # 给一个默认值
    {% endif %}
    {% if server_name is defined  %}
  server_name {{ server_name }};     # 顶格写4个就可以跟listen对齐
    {% elif server_name2 is defined %}
  server_name {{ server_name2 }};
    {% endif %}

    location / {
     root  html;
     index index.html index.htm;
   }

1.3 检查变量是否为空

{% if my_list %}
    List is not empty
{% else %}
    List is empty
{% endif %}

在 Jinja2 中,空字符串 ("")、空列表 ([])、空字典 ({})、None 都会被视为 False

2. 循环 (for 语句)

Jinja2 的 for 语句用于遍历列表、字典等数据结构。

基本语法

{% for item in some_list %}
    {{ item }}
{% endfor %}

示例

2.1 遍历列表

{% for user in users %}
    用户名: {{ user.name }}, 角色: {{ user.role }}
{% endfor %}

users 可能是:

users:
  - name: Alice
    role: Admin
  - name: Bob
    role: User

输出:

用户名: Alice, 角色: Admin
用户名: Bob, 角色: User

2.2 遍历字典

{% for key, value in my_dict.items() %}
    Key: {{ key }}, Value: {{ value }}
{% endfor %}

 my_dict 可能是:

my_dict:
  hostname: server1
  ip: 192.168.1.100

 输出:

Key: hostname, Value: server1
Key: ip, Value: 192.168.1.100

2.3 使用 loop 变量

Jinja2 提供 loop 变量来获取循环状态:

{% for user in users %}
    {{ loop.index }}. {{ user.name }} - {{ user.role }}
{% endfor %}
  • loop.index:从 1 开始的索引
  • loop.index0:从 0 开始的索引
  • loop.first:是否为第一个元素 (True / False)
  • loop.last:是否为最后一个元素 (True / False)

 2.4 反向遍历

{% for num in numbers | reverse %}
    {{ num }}
{% endfor %}

3. 过滤器 (Filters)

Jinja2 提供了 过滤器 来对变量进行操作。常见的过滤器如下:

3.1 常用过滤器

{{ some_variable | filter_name }}
过滤器作用示例
upper 转大写 "hello" → "HELLO"
lower 转小写 "HELLO" → "hello"
replace(old, new) 替换字符串 "Hello World" → "Hello Jinja"
default(value) 默认值 `undefined_variable
length 获取长度 [1, 2, 3] → 3
join(separator) 连接列表 ['a', 'b', 'c'] → "a, b, c"
sort 排序 [3, 1, 2] → [1, 2, 3]

 示例

{{ "hello world" | upper }}  {# 结果:HELLO WORLD #}
{{ my_list | join(", ") }}  {# 结果:"item1, item2, item3" #}

4. 宏 (Macros)

Jinja2 允许定义 (类似于函数),可以复用代码。

{% macro macro_name(param1, param2) %}
    宏的内容
{% endmacro %}

示例 

{% macro user_info(user) %}
    用户名: {{ user.name }}, 角色: {{ user.role }}
{% endmacro %}
# 传参,传入一个字典
{{ user_info({'name': 'Alice', 'role': 'Admin'}) }}

5. 变量赋值 (set)

在 Jinja2 模板中,可以使用 set 关键字定义变量。

{% set variable_name = value %}

6. 继承 (includeextends)

Jinja2 允许在模板中 包含其他模板,实现模板复用。

6.1 include 语法

{% include "header.j2" %}
  • 这将在当前位置插入 header.j2 的内容。

6.2 extends 继承

在 Ansible 的 Jinja2 模板系统中,extends 允许模板继承另一个模板的结构,使得多个模板可以共享一个基础模板 (base template),实现 代码复用模块化设计

1. extends 的基本原理

  • extends 用于继承一个 基础模板 (base template)。
  • 继承的模板 必须 定义 block,子模板 (child template) 可以覆盖这些 block 以填充内容。
  • 这种结构适用于 HTML 文件、配置文件、YAML/JSON 文件等,可减少代码重复,提高可维护性。

2. extends 语法

2.1 base.j2(基础模板)

{% block header %}
默认头部
{% endblock %}

{% block content %}
默认内容
{% endblock %}

{% block footer %}
默认页脚
{% endblock %}
  • 这里定义了三个 block
    • header(头部)
    • content(内容)
    • footer(页脚)
  • 这些 block 允许子模板覆盖它们。

2.2 child.j2(子模板,继承 base.j2

{% extends "base.j2" %}

{% block header %}
自定义头部
{% endblock %}

{% block content %}
这里是子模板的主要内容。
{% endblock %}

child.j2 继承了 base.j2

  • header 覆盖为 "自定义头部"
  • content 也进行了自定义。
  • footer 未覆盖,因此仍然使用 base.j2 中的默认值 ("默认页脚")。

2.3 渲染后的结果

自定义头部

这里是子模板的主要内容。

默认页脚
  • header 被子模板覆盖,显示 "自定义头部"
  • content 也被子模板覆盖。
  • footer 没有被子模板修改,因此使用基础模板的默认值 "默认页脚"

3. extends 的使用规则

1.extends 语句必须是模板的第一行

{% extends "base.j2" %}

不能有空行或其他内容,否则会报错。

2. block 只能出现在 extends 继承的模板中

{% block content %}这里是内容{% endblock %}

错误示例(没有继承 extends

{% block content %}报错:Jinja2 会认为 block 没有匹配的 extends
  • 未覆盖的 block 将使用 base 模板中的默认值

  • 如果子模板 不覆盖某个 block,那么它仍然会显示 base.j2 里定义的默认内容。

4. extends 的应用场景

4.1 生成 Nginx 配置文件

基础模板:nginx_base.j2

server {
    listen 80;
    server_name {% block server_name %}default.server{% endblock %};

    location / {
        {% block location %}
        root /var/www/html;
        {% endblock %}
    }
}

子模板:nginx_custom.j2

{% extends "nginx_base.j2" %}

{% block server_name %}
example.com
{% endblock %}

{% block location %}
root /var/www/example;
{% endblock %}

渲染结果

server {
    listen 80;
    server_name example.com;

    location / {
        root /var/www/example;
    }
}

server_namelocation 被子模板覆盖,其他内容保持不变。

4.2 生成 Ansible 配置文件

基础模板:ansible_base.j2

[defaults]
inventory = {% block inventory %}/etc/ansible/hosts{% endblock %}
timeout = {% block timeout %}10{% endblock %}

子模板:ansible_custom.j2

{% extends "ansible_base.j2" %}

{% block inventory %}
/home/user/custom_inventory
{% endblock %}

渲染结果

[defaults]
inventory = /home/user/custom_inventory
timeout = 10

inventory 被覆盖,timeout 使用默认值。

5. extends vs include 的区别

特性extendsinclude
继承关系 子模板继承父模板 只是简单插入另一个文件
block 支持 ✅ 支持 block 结构 ❌ 不能使用 block
适用于 页面结构、配置结构 静态文件、可复用片段

include 只是 直接插入 文件内容,不涉及 block 继承。如果只是插入小代码片段,include 更合适

总结

Jinja2 功能作用示例
if 逻辑判断 {% if var %} ... {% endif %}
for 循环 {% for item in list %} ... {% endfor %}
filters 数据处理 `{{ text
macro 代码复用 {% macro func() %} ... {% endmacro %}
set 变量赋值 {% set var = value %}
include 引入模板 {% include "file.j2" %}
posted @ 2025-02-20 10:17  不会跳舞的胖子  阅读(69)  评论(0)    收藏  举报