说明
ini.py是对配置文件进行解析的模块,主要处理了组,主机,组的变量,子组等关系。一个inventory中饮含 groups hosts 这两个非常重要的属性,其中有两个死的组all ungrouped组。
看看源码再理解他的配置就会很好理解,有点意思的地方是作者竟然用索引号去取值,而不是传统的for,有点搞。
继续分析
这里的逻辑是
- 打开配置文件,但配置文件是写死的
- 做基础的解析,得到all ungroupd组,并处理基本的主机主机变量组
- 处理子组,因为就是个对应关系
- 把深度为0的组加入到all组中
- 解析组的变量
测试,看一下下面的原型再看这个
cat /etc/ansible/hosts
[web]
10.1.1.2
10.1.1.3 ansible_ssh_user=zwhset ansible_ssh_port=22
[web:vars]
group=web
name=zwhset
age=18
[web:children]
nginx
tomcat
apache
[nginx]
10.1.1.4
[tomcat]
10.1.1.5
[apache]
10.1.1.6
测试,这里需要结合groupg与host来看
In [27]: from ansible.inventory.ini import InventoryParser
In [28]: inventory = InventoryParser()
In [29]: inventory.filename
Out[29]: '/etc/ansible/hosts'
In [30]: inventory.groups # 查看所有的组
Out[30]:
{'all': <ansible.inventory.group.Group at 0x1079cca10>,
'apache': <ansible.inventory.group.Group at 0x1079ccc80>,
'nginx': <ansible.inventory.group.Group at 0x1079ccb48>,
'tomcat': <ansible.inventory.group.Group at 0x1079ccc18>,
'ungrouped': <ansible.inventory.group.Group at 0x107959f58>,
'web': <ansible.inventory.group.Group at 0x1079ccae0>}
In [31]: inventory.hosts # 查看所有的主机
Out[31]:
{'10.1.1.2': <ansible.inventory.host.Host at 0x1078e9950>,
'10.1.1.3': <ansible.inventory.host.Host at 0x10795b440>,
'10.1.1.4': <ansible.inventory.host.Host at 0x1078feea8>,
'10.1.1.5': <ansible.inventory.host.Host at 0x106e70950>,
'10.1.1.6': <ansible.inventory.host.Host at 0x1078fc368>}
In [32]: # 查看一下web的子组
In [33]: web = inventory.groups["web"]
# 查看web的子组
In [36]: for g in web.child_groups:
...: print g.name
...:
nginx
tomcat
apache
# 查看web组子组的父组
In [38]: for g in web.child_groups:
...: for kg in g.parent_groups: # 查看子组的父组
...: print kg.name
...:
...:
web
web
web
# 查看web子组的主机
In [39]: for g in web.child_groups:
...: print g.hosts
...:
[<ansible.inventory.host.Host object at 0x1078feea8>]
[<ansible.inventory.host.Host object at 0x106e70950>]
[<ansible.inventory.host.Host object at 0x1078fc368>]
# 查看web子组的主机变量,前面没设
In [41]: for g in web.child_groups:
...: for h in g.hosts:
...: print h.vars
...:
{}
{}
{}
# 唯一的一个组变量,在这里被成功解析
In [42]: for h in web.hosts:
...: print h.vars
...:
{}
{'ansible_ssh_port': 22, 'ansible_ssh_user': 'zwhset'}
模块原型
import ansible.constants as C
from ansible.inventory.host import Host
from ansible.inventory.group import Group
from ansible.inventory.expand_hosts import detect_range
from ansible.inventory.expand_hosts import expand_hostname_range
from ansible import errors
from ansible import utils
import shlex
import re
import ast
class InventoryParser(object):
"""
Host inventory for ansible.
"""
# 解析配置文件
def __init__(self, filename=C.DEFAULT_HOST_LIST):
# 获取一个文件对象
with open(filename) as fh:
self.filename = filename
# 所有行的记录,一个列表,将要对这个列表进行解析,也就是配置文件的每一行
self.lines = fh.readlines()
self.groups = {}
self.hosts = {}
# 实例化的时候会角化_parse方法
self._parse()
# 执行一堆函数,然后返回groups
def _parse(self):
# 对配置文件进行一个解析,最后得到实例化的所有东西 一个all一个ungroupd
# 这是处理基础的主机以及变量,并没有对组关系进行处理
self._parse_base_groups()
# 1234 再来一次,处理子组
self._parse_group_children()
# 把深度为0并且不是all组的添加进all组
self._add_allgroup_children()
# 解析组的变量
self._parse_group_variables()
return self.groups
@staticmethod
def _parse_value(v):
# 变量的value不包含#
if "#" not in v:
try:
# 安全值的检查,
ret = ast.literal_eval(v)
# 符点转换
if not isinstance(ret, float):
# Do not trim floats. Eg: "1.20" to 1.2
return ret
# Using explicit exceptions.
# Likely a string that literal_eval does not like. We wil then just set it.
except ValueError:
# For some reason this was thought to be malformed.
pass
except SyntaxError:
# Is this a hash with an equals at the end?
pass
return v
# [webservers]
# alpha
# beta:2345
# gamma sudo=True user=root
# delta asdf=jkl favcolor=red
def _add_allgroup_children(self):
# 获取groups的所有的值
for group in self.groups.values():
# 如果深度为0 并且组名不等于all的,添加到all组
# 那么深度不为0的呢,不知道深度的可以看一下group的方法
if group.depth == 0 and group.name != 'all':
self.groups['all'].add_child_group(group)
def _parse_base_groups(self):
# FIXME: refactor
# 定义ungrouped all组名, 并在all里添加一个ungrouped组
ungrouped = Group(name='ungrouped')
all = Group(name='all')
all.add_child_group(ungrouped)
self.groups = dict(all=all, ungrouped=ungrouped)
active_group_name = 'ungrouped' # 活动的组,没啥好说的
# 这里没用啥黑科技,使用range + len获取的其实就是文件的索引号
for lineno in range(len(self.lines)):
# 取 #号之前的字符串,然后消除两边的空白
line = utils.before_comment(self.lines[lineno]).strip()
# 如果字符串开始是[*]这种形式表明就是一种组
if line.startswith("[") and line.endswith("]"):
# 把[]去除掉,拿中间的*
active_group_name = line.replace("[","").replace("]","")
# 如果是变量或或是子组的方式
if ":vars" in line or ":children" in line:
# 组名取:vars 左侧分割的,即 [webs:vars] [web:children]取web
active_group_name = active_group_name.rsplit(":", 1)[0]
# 这里就是检查一下组名存不存在组里面,没有存在就添加
if active_group_name not in self.groups:
new_group = self.groups[active_group_name] = Group(name=active_group_name)
active_group_name = None
elif active_group_name not in self.groups:
new_group = self.groups[active_group_name] = Group(name=active_group_name)
# 如果是空行或者;开头的就当成注释,注意这里有前面是#号分割的,拿#号之前的
# 所以就不需要就判断#号了
elif line.startswith(";") or line == '':
pass
# 这肯定是真,因为前面定义了,而且走的是elif, 这里针对的是不以[]就不是组的
elif active_group_name:
# 一个处理类shell的解析方式
tokens = shlex.split(line)
# 空则跳到下一循环
if len(tokens) == 0:
continue
# 拿到主机名,并默认定义一个端口
hostname = tokens[0]
port = C.DEFAULT_REMOTE_PORT
# Three cases to check:
# 0. A hostname that contains a range pesudo-code and a port
# 1. A hostname that contains just a port
# 如果主机名中包含:大于1,即为IPV6的地址, XXX:XXX::XXX.port
if hostname.count(":") > 1:
# Possible an IPv6 address, or maybe a host line with multiple ranges
# IPv6 with Port XXX:XXX::XXX.port
# FQDN foo.example.com
if hostname.count(".") == 1:
(hostname, port) = hostname.rsplit(".", 1)
# 取主机名和端口
elif ("[" in hostname and
"]" in hostname and
":" in hostname and
(hostname.rindex("]") < hostname.rindex(":")) or
("]" not in hostname and ":" in hostname)):
(hostname, port) = hostname.rsplit(":", 1)
# 定义一个字的主机组
hostnames = []
# 这里是处理这种[a-z] [1-3]这种主机名,会返回匹配关系的主机名的
if detect_range(hostname):
hostnames = expand_hostname_range(hostname)
else:
hostnames = [hostname]
# 遍历一下
for hn in hostnames:
host = None
# 判断是否已经存在, 存在更新变量host,这里是以已经存在的为准
if hn in self.hosts:
host = self.hosts[hn]
else:
# 一个实例化Host,更新一下实例的hosts列表
host = Host(name=hn, port=port)
self.hosts[hn] = host
# 环境变量,即配置文件里面定义什么密码帐号之类的玩意儿
if len(tokens) > 1:
for t in tokens[1:]:
# 带#号就跳出
if t.startswith('#'):
break
try:
# kv型式,在后面设置主机的变量
(k,v) = t.split("=", 1)
except ValueError, e:
raise errors.AnsibleError("%s:%s: Invalid ini entry: %s - %s" % (self.filename, lineno + 1, t, str(e)))
host.set_variable(k, self._parse_value(v))
# 添加主机在 ungrouped里面,由于指针的关系,所以all里面就会有
self.groups[active_group_name].add_host(host)
# [southeast:children]
# atlanta
# raleigh
def _parse_group_children(self):
group = None
#再来一次,注意用的是索引号,有更好的方式
for lineno in range(len(self.lines)):
# 利用索引取到值,这写的就尴尬,并去除两边的空格
line = self.lines[lineno].strip()
# 空行跳到下一次
if line is None or line == '':
continue
# 注意这里,是专门处理[:children]子组的往下看
if line.startswith("[") and ":children]" in line:
# 同样清空 [ :children] 留下的就是组名
line = line.replace("[","").replace(":children]","")
#判断一下组名是否在组里,没有就加呗
group = self.groups.get(line, None)
if group is None:
group = self.groups[line] = Group(name=line)
# 同理
elif line.startswith("#") or line.startswith(";"):
pass
elif line.startswith("["):
group = None
# 如果匹配第一个if是子组,第二次循环group就为真了
elif group:
# 就是添加子组
kid_group = self.groups.get(line, None)
if kid_group is None:
raise errors.AnsibleError("%s:%d: child group is not defined: (%s)" % (self.filename, lineno + 1, line))
else:
group.add_child_group(kid_group)
# [webservers:vars]
# http_port=1234
# maxRequestsPerChild=200
def _parse_group_variables(self):
group = None
for lineno in range(len(self.lines)):
line = self.lines[lineno].strip()
# [web:vars]这种形式,因为前面添加过组,所组应该是存在的,如果不存在就报错呗
if line.startswith("[") and ":vars]" in line:
line = line.replace("[","").replace(":vars]","")
group = self.groups.get(line, None)
if group is None:
raise errors.AnsibleError("%s:%d: can't add vars to undefined group: %s" % (self.filename, lineno + 1, line))
# 跳过
elif line.startswith("#") or line.startswith(";"):
pass
# [开头的前面处理过了这里不处理,只处理组的变量
elif line.startswith("["):
group = None
elif line == '':
pass
# 如果匹配到了即代表这是组的变量设置
elif group:
# 必须是用=号来进行赋值的
if "=" not in line:
raise errors.AnsibleError("%s:%d: variables assigned to group must be in key=value form" % (self.filename, lineno + 1))
else:
# 走K value
(k, v) = [e.strip() for e in line.split("=", 1)]
# 这里用到组的方法设置一个字典其实就是一个字典
# 在这里会检查一下值是不是一些不安全的东西
group.set_variable(k, self._parse_value(v))
def get_host_variables(self, host):
return {}
本文转自 煮酒品茶 51CTO博客,原文链接:http://blog.51cto.com/cwtea/2043637,如需转载请自行联系原作者