15. Ansible文件管理模块及Jinja2过滤器
目录
常用文件管理模块
1. file
我们在讲ansible ad-hoc的时候,已经说过file模块,在playbook中的使用也没什么不同,下面给个简单的示例:
- name: Touch a file and set permissions
file:
path: /path/to/file
owner: user1
group: group1
mode: 0640
state: touch
2. synchronize
synchronize模块示例:
- name: synchronize local file to remote files
synchronize:
src: file
dest: /path/to/file
3. copy
同样的,我们已经介绍过copy模块,示例如下:
- name: copy a file to managed hosts
copy:
src: file
dest: /path/to/file
4. fetch
fetch模块与copy模块正好相反,copy是把主控端的文件复制到被控端,而fetch则是把被控端的文件复制到主控端。并且在主控端指定的目录下,以被控端主机名的形式来组织目录结构。
- name: Use the fetch module to retrieve secure log files
hosts: all
user: ansible
tasks:
- name: Fetch the /var/log/secure log file from managed hosts
fetch:
src: /var/log/secure
dest: secure-backups
flat: no
在主控端文件存储的目录树如下:
# tree secure-backups/
secure-backups/
└── 10.1.61.187
└── var
└── log
└── secure
3 directories, 1 file
参考:https://docs.ansible.com/ansible/latest/modules/fetch_module.html#fetch-module
5. lineinfile
lineinfile是一个非常有用的模块,而且相对来说,也是用法比较复杂的模块,可直接参考《Ansible lineinfile模块》
6. stat
stat模块与linux中的stat命令一样,用来显示文件的状态信息。
- name: Verify the checksum of a file
stat:
path: /path/to/file
checksum_algorithm: md5
register: result
- debug:
msg: "The checksum of the file is {{ result.stat.checksum }}"
参考: https://docs.ansible.com/ansible/latest/modules/stat_module.html#stat-module
7. blockinfile
围绕着被标记的行插入、更新、删除一个文本块。
#cat files/test.html
<html>
<head>
</head>
<body>
</body>
</html>
#cat blockinfile_ex.yml
---
- name: blockinfile module test
hosts: test
tasks:
- name: copy test.html to dest
copy:
src: files/test.html
dest: /var/www/html/test.html
- name: add block
blockinfile:
marker: "<!-- {mark} ANSIBLE MANAGED BLOCK -->"
insertafter: "<body>"
path: /var/www/html/test.html
block: |
<h1>Welcome to {{ ansible_hostname }}</h1>
<p>Last updated on {{ ansible_date_time.iso8601 }}</p>
执行后结果如下:
[root@app html]# cat test.html
<html>
<head>
</head>
<body>
<!-- BEGIN ANSIBLE MANAGED BLOCK -->
<h1>Welcome to app</h1>
<p>Last updated on 2019-05-28T15:00:03Z</p>
<!-- END ANSIBLE MANAGED BLOCK -->
</body>
</html>
更多blockinfile用法参考:https://docs.ansible.com/ansible/latest/modules/blockinfile_module.html#blockinfile-module
Jinja2模板管理
Jinja2简介
Jinja2是基于python的模板引擎。那么什么是模板?
假设说现在我们需要一次性在10台主机上安装redis,这个通过playbook现在已经很容易实现。默认情况下,所有的redis安装完成之后,我们可以统一为其分发配置文件。这个时候就面临一个问题,这些redis需要监听的地址各不相同,我们也不可能为每一个redis单独写一个配置文件。因为这些配置文件中,绝大部分的配置其实都是相同的。这个时候最好的方式其实就是用一个通用的配置文件来解决所有的问题。将所有需要修改的地方使用变量替换,如下示例中redis.conf.j2文件:
daemonize yes
supervised systemd
pidfile /var/run/redis.pid
port 6379
logfile "/var/log/redis/redis.log"
dbfilename dump.rdb
dir /data/redis
maxmemory {{ (ansible_memtotal_mb /2) | int }}M
bind {{ ansible_eth0.ipv4.address }} 127.0.0.1
timeout 300
loglevel notice
databases 16
save 900 1
save 300 10
save 60 10000
rdbcompression yes
maxclients 10000
appendonly yes
appendfilename appendonly.aof
appendfsync everysec
那么此时,redis.conf.j2文件就是一个模板文件。{{ ansible_eth0.ipv4.address }}
是一个fact变量,用于获取被控端ip地址以实现替换。
在playbook中使用jinja2
现在我们有了一个模板文件,那么在playbook中如何来使用呢?
playbook使用template模块来实现模板文件的分发,其用法与copy模块基本相同,唯一的区别是,copy模块会将原文件原封不动的复制到被控端,而template会将原文件复制到被控端,并且使用变量的值将文件中的变量替换以生成完整的配置文件。
下面是一个完整的示例:
# cat config_redis.yml
- name: Configure Redis
hosts: test
tasks:
- name: create redis group
group:
name: redis
gid: 1111
- name: create redis user
user:
name: redis
uid: 1111
group: redis
- name: install redis
yum:
name: redis
state: present
- name: create data dir
file:
path: /data/redis
state: directory
recurse: yes
owner: redis
group: redis
- name: copy redis.conf to dest
template:
src: templates/redis.conf.j2
dest: /etc/redis.conf
notify:
- restart redis
- name: start redis
service:
name: redis
state: started
enabled: yes
handlers:
- name: restart redis
service:
name: redis
state: restarted
执行完成之后,我们可以看到被控端/etc/redis.conf配置文件如下:
daemonize yes
pidfile /var/run/redis.pid
port 6379
logfile "/var/log/redis/redis.log"
dbfilename dump.rdb
dir /data/redis
maxmemory 1G
bind 10.1.61.187 127.0.0.1
timeout 300
loglevel notice
databases 16
save 900 1
save 300 10
save 60 10000
rdbcompression yes
maxclients 10000
appendonly yes
appendfilename appendonly.aof
appendfsync everysec
关于template模块的更多参数说明:
backup:如果原目标文件存在,则先备份目标文件
dest:目标文件路径
force:是否强制覆盖,默认为yes
group:目标文件属组
mode:目标文件的权限
owner:目标文件属主
src:源模板文件路径
validate:在复制之前通过命令验证目标文件,如果验证通过则复制
Jinja2条件语句
在上面的示例中,我们直接取了被控节点的eth0网卡的ip作为其监听地址。那么假如有些机器的网卡是bond0,这种做法就会报错。这个时候我们就需要在模板文件中定义条件语句如下:
daemonize yes
pidfile /var/run/redis.pid
port 6379
logfile "/var/log/redis/redis.log"
dbfilename dump.rdb
dir /data/redis
maxmemory 1G
{% if ansible_bond0 is defined %}
bind {{ ansible_bond0.ipv4.address }} 127.0.0.1
{% elif ansible_eth0 is defined %}
bind {{ ansible_eth0.ipv4.address }} 127.0.0.1
{% else%}
bind 0.0.0.0
{% endif %}
timeout 300
loglevel notice
databases 16
save 900 1
save 300 10
save 60 10000
rdbcompression yes
maxclients 10000
appendonly yes
appendfilename appendonly.aof
appendfsync everysec
我们可以更进一步,让redis主从角色都可以使用该文件:
daemonize yes
pidfile /var/run/redis.pid
port 6379
logfile "/var/log/redis/redis.log"
dbfilename dump.rdb
dir /data/redis
maxmemory {{ (ansible_memtotal_mb /2) | int }}M
{% if ansible_bond0 is defined %}
bind {{ ansible_bond0.ipv4.address }} 127.0.0.1
{% elif ansible_bond0 is defined %}
bind {{ ansible_bond0.ipv4.address }} 127.0.0.1
{% else%}
bind 0.0.0.0
{% endif %}
{% if masterip is defined %}
slaveof {{ hostvars[groups.redismaster.0].ansible_bond0.ipv4.address }} {{ masterport|default(6379) }}
{% endif %}
{% if masterpass is defined %}
masterauth {{ masterpass }}
{% endif %}
{% if requirepass is defined %}
requirepass {{ requirepass }}
{% endif %}
#requirepass是配置在主节点的,masterauth是配置在从节点的,两边配置要一样从节点才能和主节点连接上进行主从复制。
timeout 300
loglevel notice
databases 16
save 900 1
save 300 10
save 60 10000
rdbcompression yes
maxclients 10000
appendonly yes
appendfilename appendonly.aof
appendfsync everysec
stop-writes-on-bgsave-error no
我们定义一个inventory如下:
all:
children:
redis:
children:
redismaster:
vars:
requirepass: redhat
hosts:
servera:
redisslave:
vars:
masterauth: redhat
masterport: 6379
masterip: yes
hosts:
serverb:
serverc:
测试
[root@servera ~]# redis-cli
127.0.0.1:6379> auth redhat
127.0.0.1:6379> info
role:master
[root@serverb ~]# redis-cli
127.0.0.1:6379> info
role:slave
Jinja2循环语句
定义一个inventory示例如下:
[proxy]
10.1.61.195
[webservers]
10.1.61.27
10.1.61.187
现在把proxy主机组中的主机作为代理服务器,安装nginx做反向代理,将请求转发至后面的两台webserver,即webserver组的服务器。
现在我们编写一个playbook如下:
#cat config_nginx.conf
# 还需要在ansible.cfg中开启facts缓存
- name: gather facts
gather_facts: Fasle
hosts: webservers
tasks:
- name: gather facts
setup:
- name: Configure Nginx
hosts: proxy
tasks:
- name: install nginx
yum:
name: nginx
state: present
- name: copy nginx.conf to dest
template:
src: templates/nginx.conf.j2
dest: /etc/nginx/nginx.conf
notify:
- restart nginx
- name: start nginx
service:
name: nginx
state: started
enabled: yes
handlers:
- name: restart nginx
service:
name: nginx
state: restarted
模板文件 templates/nginx.conf.j2示例如下:
# cat nginx.conf.j2
user nginx;
worker_processes {{ ansible_processor_vcpus }};
error_log /var/log/nginx/error.log;
pid /var/run/nginx.pid;
include /usr/share/nginx/modules/*.conf;
events {
worker_connections 65535;
use epoll;
}
http {
map $http_x_forwarded_for $clientRealIP {
"" $remote_addr;
~^(?P<firstAddr>[0-9\.]+),?.*$ $firstAddr;
}
log_format real_ip '{ "datetime": "$time_local", '
'"remote_addr": "$remote_addr", '
'"source_addr": "$clientRealIP", '
'"x_forwarded_for": "$http_x_forwarded_for", '
'"request": "$request_uri", '
'"status": "$status", '
'"request_method": "$request_method", '
'"request_length": "$request_length", '
'"body_bytes_sent": "$body_bytes_sent", '
'"request_time": "$request_time", '
'"http_referrer": "$http_referer", '
'"user_agent": "$http_user_agent", '
'"upstream_addr": "$upstream_addr", '
'"upstream_status": "$upstream_status", '
'"upstream_http_header": "$upstream_http_host",'
'"upstream_response_time": "$upstream_response_time", '
'"x-req-id": "$http_x_request_id", '
'"servername": "$host"'
' }';
access_log /var/log/nginx/access.log real_ip;
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;
include /etc/nginx/mime.types;
default_type application/octet-stream;
include /etc/nginx/conf.d/*.conf;
upstream web {
{% for host in groups['webservers'] %}
server {{ hostvars[host].ansible_ens33.ipv4.address }}:80;
{% endfor %}
}
server {
listen 80 default_server;
server_name _;
location / {
proxy_pass http://web;
}
}
}
for循环只能循环列表,不能循环字典,字典需要转换为列表才能循环
Jinja2过滤器
1. default过滤器
当指定的变量不存在时,用于设定默认值
简单示例:
"Host": "{{ db_host | default('lcoalhost') }}"
复杂一点儿的实例:
- hosts:
gather_facts: false
vars:
paths:
- path: /tmp/test
mode: '0400'
- path: /tmp/foo
- path: /tmp/bar
tasks:
- file:
path: "{{ item.path }}"
state: touch
mode: "{{ item.mode | default(omit)}}"
with_items: "{{ paths }}"
2. 字符串操作相关的过滤器
upper:将所有字符串转换为大写
lower:将所有字符串转换为小写
capitalize:将字符串的首字母大写,其他字母小写
reverse:将字符串倒序排列
first:返回字符串的第一个字符
last:返回字符串的最后一个字符
trim:将字符串开头和结尾的空格去掉
center(30):将字符串放在中间,并且字符串两边用空格补齐30位
length:返回字符串的长度,与count等价
list:将字符串转换为列表
shuffle:list将字符串转换为列表,但是顺序排列,shuffle同样将字符串转换为列表,但是会随机打乱字符串顺序
示例:
- hosts: test
gather_facts: no
vars:
teststr: "abc123ABV"
teststr1: " abc "
teststr2: "123456789"
teststr3: "sfacb1335@#$%"
tasks:
- debug:
msg: "{{ teststr | upper }}"
- debug:
msg: "{{ teststr | lower }}"
- debug:
msg: "{{ teststr | capitalize }}"
- debug:
msg: "{{ teststr | reverse }}"
- debug:
msg: "{{ teststr|first }}"
- debug:
msg: "{{ teststr|last }}"
- debug:
msg: "{{ teststr1 | trim }}"
- debug:
msg: "{{ teststr2 | center(30) }}"
- debug:
msg: "{{ teststr2 | length }}"
- debug:
msg: "{{ teststr3 | list }}"
- debug:
msg: "{{ teststr3 | shuffle }}"
3. 数字操作相关的过滤器
int: 将对应的值转换为整数
float:将对应的值转换为浮点数
abs:获取绝对值
round:小数点四舍五入
random:从一个给定的范围中获取随机值
示例:
- hosts: test
gather_facts: no
vars:
testnum: -1
tasks:
- debug:
msg: "{{ 8+('8'|int) }}"
- debug:
# 默认情况下,如果无法完成数字转换则返回0
# 这里指定如果无法完成数字转换则返回6
msg: "{{ 'a'|int(default=6) }}"
- debug:
msg: "{{ '8'|float }}"
- debug:
msg: "{{ 'a'|float(8.88)' }}"
- debug:
msg: "{{ testnum|abs }}"
- debug:
msg: "{{ 12.5|round }}"
- debug:
msg: "{{ 3.1415926 | round(5) }}"
- debug:
# 从0到100中随机返回一个数字
msg: "{{ 100|random }}"
- debug:
# 从5到10中随机返回一个数字
msg: "{{ 10|random(start=5) }}"
- debug:
# 从4到15中随机返回一个数字,步长为3
# 返回的随机数只可能是:4,7,10,13中的一个
msg: "{{ 15|random(start=4,step=3) }}"
- debug:
# 从0到15随机返回一个数字,步长为4
msg: "{{ 15|random(step=4) }}"
4. 列表操作相关的过滤器
length: 返回列表长度
first:返回列表的第一个值
last:返回列表的最后一个值
min:返回列表中最小的值
max:返回列表中最大的值
sort:重新排列列表,默认为升序排列,sort(reverse=true)为降序
sum:返回纯数字非嵌套列表中所有数字的和
flatten:如果列表中包含列表,则flatten可拉平嵌套的列表,levels参数可用于指定被拉平的层级
join:将列表中的元素合并为一个字符串
random:从列表中随机返回一个元素
shuffle
upper
lower
union:将两个列表合并,如果元素有重复,则只留下一个
intersect:获取两个列表的交集
difference:获取存在于第一个列表中,但不存在于第二个列表中的元素
symmetric_difference:取出两个列表中各自独立的元素,如果重复则只留一个
示例:
- hosts: test
gather_facts: false
vars:
testlist1: [1,2,4,6,3,5]
testlist2: [1,[2,3,4,[5,6]]]
testlist3: [1,2,'a','b']
testlist4: [1,'A','b',['C','d'],'Efg']
testlist5: ['abc',1,2,'a',3,2,'1','abc']
testlist6: ['abc',3,'1','b','a']
tasks:
- debug:
msg: "{{ testlist1 | length }}"
- debug:
msg: "{{ testlist1 |first }}"
- debug:
msg: "{{ testlist1 | last }}"
- debug:
msg: "{{ testlist1 | min }}"
- debug:
msg: "{{ testlist1 | max }}"
- debug:
msg: "{{ testlist1 | sort }}"
- debug:
msg: "{{ testlist1 | sort(reverse=true) }}"
- debug:
msg: "{{ testlist2 | flatten }}"
- debug:
msg: "{{ testlist2 | flatten(levels=1) }}"
- debug:
msg: "{{ testlist2 | flatten | max }}"
- debug:
msg: "{{ testlist3 | join }}"
- debug:
msg: "{{ testlist3 |join(',')}}"
- debug:
msg: "{{ testlist3 | random }}"
- debug:
msg: "{{ testlist3 | shuffle }}"
- debug:
msg: "{{ testlist4 | upper }}"
- debug:
msg: "{{ testlist4 | lower }}"
- debug:
msg: "{{ testlist5 | union(testlist6) }}"
- debug:
msg: "{{ testlist5 | intersect(testlist6) }}"
- debug:
msg: "{{ testlist5 | difference(testlist6) }}"
- debug:
msg: "{{ testlist5 | symmetric_difference(testlist6) }}"
5. hash和Encoding过滤器
- hash过滤器
- hosts: test
gather_facts: no
tasks:
- debug:
msg: "{{ 'redhat' | hash('sha1') }}"
#结果:
ok: [servera] => {
"msg": "3c767c41afb12ada140190ed82db3fd930e2efa3"
#相当于:
# echo -n redhat | sha1sum
3c767c41afb12ada140190ed82db3fd930e2efa3 -
- password_hash过滤器
- hosts: test
gather_facts: no
tasks:
- user:
name: john
password: "{{ password }}"
vars:
password: "{{ 'redhat' | password_hash('sha512') }}"
- base64加密和解密
- hosts: test
gather_facts: no
tasks:
- debug:
msg: "{{ 'redhat' | b64encode | b64decode }}"
b64encode: 加密
b64decode: 解密
# 相当于:
echo -n redhat | base64 -w 0 | base64 -d
6. 查找替换过滤器
- replace过滤器
- hosts: test
gather_facts: no
tasks:
- debug:
msg: "{{ 'marvin, arthur' | replace('ar','**') }}"
- regex_search 过滤器
- hosts: test
gather_facts: no
tasks:
- debug:
msg: "{{ 'marvin, arthur' | regex_search('ar\\S*r') }}"
\是特殊字符,需要特殊处理以后再交给正则表达式
- regex_replace过滤器
- hosts: test
gather_facts: no
vars:
str1: 'marvin, arthur'
tasks:
- debug:
msg: "{{ str1 | regex_replace('ar(\\S*)r','\\1mb') }}"
正则表达式:
\w 匹配字母数字及下划线
\W 匹配非字母数字及下划线
\s 匹配任意空白字符,等价于 [ \t\n\r\f]。
\S 匹配任意非空字符
\d 匹配任意数字,等价于 [0-9].
\D 匹配任意非数字
\A 匹配字符串开始
\Z 匹配字符串结束,如果是存在换行,只匹配到换行前的结束字符串。
\z 匹配字符串结束
\G 匹配最后匹配完成的位置。
\b 匹配一个单词边界,也就是指单词和空格间的位置。例如, 'er\b' 可以匹配"never" 中的 'er',但不能匹配 "verb" 中的 'er'。
\B 匹配非单词边界。'er\B' 能匹配 "verb" 中的 'er',但不能匹配 "never" 中的 'er'。
\n, \t, 等. 匹配一个换行符。匹配一个制表符。等
\1...\9 匹配第n个分组的内容。
\10 匹配第n个分组的内容,如果它经匹配。否则指的是八进制字符码的表达式。
7. 应用于字典变量的过滤器
- combine 将两个字典合成一个
- hosts: servera
gather_facts: no
vars:
user1:
name: bob
uid: 1200
userinfo:
home: /home/bob
comment: "user account"
tasks:
- name: combine
debug:
msg: "{{ user1 | combine(userinfo)}}"
- dict2items 将字典转换为列表
示例一:
tags:
Application: payment
Environment: dev
# 转化后:
{{ dict | dict2items }}
- key: Application
value: payment
- key: Environment
value: dev
示例二:
files:
users: /etc/passwd
groups: /etc/group
{{ files | dict2items(key_name='file', value_name='path') }}
# 转化后:
- file: users
path: /etc/passwd
- file: groups
path: /etc/group
- items2dict 将列表转换为字典
示例一:
tags:
- key: Application
value: payment
- key: Environment
value: dev
{{ tags | items2dict }}
# 转化后:
Application: payment
Environment: dev
示例二:
fruits:
- fruit: apple
color: red
- fruit: pear
color: yellow
- fruit: grape
color: yellow
{{ fruits | items2dict(key_name='fruit', value_name='color') }}
# 必须指定key_name和value_name,否则会报KeyError: 'key'或者
# 转化后:
"apple": "red",
"grapefruit": "yellow",
"pear": "yellow"
- json_query 将变量转换成json,并查询子元素
- hosts: localhost
gather_facts: no
vars:
domain_definition: "{{ lookup('file','json_example.json') | from_json }}"
tasks:
- name: "Display all cluster names"
debug:
var: item
loop: "{{ domain_definition | json_query('domain.cluster[*].name') }}"
- from_json/from_yaml
{{ some_variable | from_json }}
{{ some_variable | from_yaml }}
- hosts: localhost
gather_facts: no
tasks:
- name: from_json filter test
debug:
msg: "{{ domain_definition }}"
vars:
domain_definition: "{{ lookup('file','json_example.json') | from_json }}"
- to_json/to_yaml/to_nice_json/to_nice_yaml
{{ some_variable | to_json }}
{{ some_variable | to_yaml }}
{{ some_variable | to_nice_json }}
{{ some_variable | to_nice_yaml }}
- name: Convert between JSON and YAML format
vars:
hosts:
- name: bastion
ip:
- 172.25.250.254
- 172.25.252.1
debug:
msg: '{{ hosts | to_json }}'
# 结果:
'[{"name": "bastion", "ip": ["172.25.250.254", "172.25.252.1"]}]'
示例变量:
# cat json_example.json
{
"domain": {
"cluster": [
{
"name": "cluster1"
},
{
"name": "cluster2"
}
],
"server": [
{
"name": "server11",
"cluster": "cluster1",
"port": "8080"
},
{
"name": "server12",
"cluster": "cluster1",
"port": "8090"
},
{
"name": "server21",
"cluster": "cluster2",
"port": "9080"
},
{
"name": "server22",
"cluster": "cluster2",
"port": "9090"
}
],
"library": [
{
"name": "lib1",
"target": "cluster1"
},
{
"name": "lib2",
"target": "cluster2"
}
]
}
}
8. ipaddr过滤器处理网络地址
ipaddr过滤器主要用于处理网络地址, 其支持的过滤器参数如下:
address
host
prefix
size
network
netmask
broadcast
subnet
ipv4 and ipv6
要想使用ipaddr过滤器,需要在主控端安装python-netaddr包:
yum install -y python-netaddr
示例:
- hosts: localhost
gather_facts: no
vars:
my_ip_addr: ["192.168.0.1/24"]
tasks:
- name: ipaddr
debug:
msg: "{{ my_ip_addr | ipaddr }}"
- name: ipaddr(network)
debug:
msg: "{{ my_ip_addr | ipaddr('network') }}"
- name: ipaddr(host)
debug:
msg: "{{ my_ip_addr | ipaddr('host') }}"
- name: ipaddr(netmask)
debug:
msg: "{{ my_ip_addr | ipaddr('netmask') }}"
- name: ipaddr(size)
debug:
msg: "{{ my_ip_addr | ipaddr('size') }}"
- name: ipaddr(subnet)
debug:
msg: "{{ my_ip_addr | ipaddr('subnet') }}"
- name: ipaddr(ipv4)
debug:
msg: "{{ my_ip_addr | ipaddr('ipv4') }}"
- name: ipaddr(broadcast)
debug:
msg: "{{ my_ip_addr | ipaddr('broadcast') }}"
输出:
TASK [ipaddr] *********************************************************************
ok: [localhost] => {
"msg": [
"192.168.0.1/24"
]
}
TASK [ipaddr(network)] ************************************************************
ok: [localhost] => {
"msg": [
"192.168.0.0"
]
}
TASK [ipaddr(host)] ***************************************************************
ok: [localhost] => {
"msg": [
"192.168.0.1/24"
]
}
TASK [ipaddr(netmask)] ************************************************************
ok: [localhost] => {
"msg": [
"255.255.255.0"
]
}
TASK [ipaddr(size)] ***************************************************************
ok: [localhost] => {
"msg": [
256
]
}
TASK [ipaddr(subnet)] *************************************************************
ok: [localhost] => {
"msg": [
"192.168.0.0/24"
]
}
TASK [ipaddr(ipv4)] ***************************************************************
ok: [localhost] => {
"msg": [
"192.168.0.1/24"
]
}
TASK [ipaddr(broadcast)] **********************************************************
ok: [localhost] => {
"msg": [
"192.168.0.255"
]
}
9. url分割过滤器
urlspilt用于分割url,并取出相应字段
- hosts: localhost
gather_facts: no
vars:
url: 'http://user:password@www.example.com:8080/xxx/index.html?query=132'
tasks:
- name: "get url hostname"
debug:
msg: "{{ url | urlsplit('hostname') }}"
# ==> "msg": "www.example.com"
- name: "get url netloc"
debug:
msg: "{{ url | urlsplit('netloc') }}"
# ==> "user:password@www.example.com:8080"
- name: "get url path"
debug:
msg: "{{ url | urlsplit('path') }}"
# ==> "msg": "/xxx/index.html"
- name: "get url port"
debug:
msg: "{{ url | urlsplit('port') }}"
# ==> "msg": "8080"
- name: "get url scheme"
debug:
msg: "{{ url | urlsplit('scheme') }}"
# ==> "msg": "http"
- name: "get url query"
debug:
msg: "{{ url | urlsplit('query') }}"
# ==> "msg": "query=132"
10. 应用于注册变量的过滤器
正常情况下,当某个task执行失败的时候,ansible会中止运行。此时我们可以通过ignore_errors
来捕获异常以让task继续往下执行。然后调用debug模块打印出出错时的内容,拿来错误结果后,主动失败。
- name: Run myprog
command: /opt/myprog
register: result
ignore_errors: True
- debug:
var: result
- debug:
msg: "Stop running the playbook if myprog failed"
failed_when: result|failed
任务返回值过滤器:
failed: 如果注册变量的值是任务failed则返回True
changed: 如果注册变量的值是任务changed则返回True
success:如果注册变量的值是任务succeeded则返回True
skipped:如果注册变量的值是任务skipped则返回True
在ansible2.9中,该方式会被废弃,不推荐使用
11. 应用于文件路径的过滤器
basename:返回文件路径中的文件名部分
dirname:返回文件路径中的目录部分
expanduser:将文件路径中的~替换为用户目录
realpath:处理符号链接后的文件实际路径
下面是一个示例:
- name: test basename
hosts: servera
vars:
homepage: ~/index.html
linkpath: /etc/systemd/system/default.target
tasks:
- name: copy homepage
copy:
src: index.html
dest: "{{ homepage }}"
- debug:
msg: "{{ homepage | basename }}"
- debug:
msg: "{{ homepage | dirname }}"
- debug:
msg: "{{ homepage | expanduser }}"
- debug:
msg: "{{ linkpath | realpath }}"
12. 自定义过滤器
举个简单的例子,现在有一个playbook如下:
- name: test filter
hosts: test
vars:
domains: ["www.example.com","example.com"]
tasks:
template:
src: templates/test.conf.j2
dest: /tmp/test.conf
templates/test.conf.j2如下:
hosts = [{{ domains | join(',') }}]
执行playbook后,在目标机上的test.conf如下:
hosts = [www.example.com,example.com]
现在如果希望目标机上的test.conf文件返回结果如下:
hosts = ["www.example.com","example.com"]
没有现成的过滤器来帮我们做这件事情。我们可以自己简单写一个surround_by_quote.py内容如下:
# 定义过滤器执行的操作
def surround_by_quote(a_list):
return ['"%s"' % an_element for an_element in a_list]
class FilterModule(object):
def filters(self):
return {'surround_by_quote': surround_by_quote}
我们需要开启ansible.cfg的配置项:
filter_plugins = /usr/share/ansible/plugins/filter
将刚刚编写的代码文件放入/usr/share/ansible/plugins/filter目录下,然后修改templates/test.conf.j2如下:
hosts = [{{ domains | join(',') |surround_by_quote }}]
再次执行playbook,最后返回结果:
hosts = ["www.example.com","example.com"]
关于jinja2更多用法参考:http://docs.jinkan.org/docs/jinja2/