说明
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 = {}
self._parse()
def _parse(self):
self._parse_base_groups()
self._parse_group_children()
self._add_allgroup_children()
self._parse_group_variables()
return self.groups
@staticmethod
def _parse_value(v):
if "#" not in v:
try:
ret = ast.literal_eval(v)
if not isinstance(ret, float):
return ret
except ValueError:
pass
except SyntaxError:
pass
return v
def _add_allgroup_children(self):
for group in self.groups.values():
if group.depth == 0 and group.name != 'all':
self.groups['all'].add_child_group(group)
def _parse_base_groups(self):
ungrouped = Group(name='ungrouped')
all = Group(name='all')
all.add_child_group(ungrouped)
self.groups = dict(all=all, ungrouped=ungrouped)
active_group_name = 'ungrouped'
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:
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 active_group_name:
tokens = shlex.split(line)
if len(tokens) == 0:
continue
hostname = tokens[0]
port = C.DEFAULT_REMOTE_PORT
if hostname.count(":") > 1:
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 = []
if detect_range(hostname):
hostnames = expand_hostname_range(hostname)
else:
hostnames = [hostname]
for hn in hostnames:
host = None
if hn in self.hosts:
host = self.hosts[hn]
else:
host = Host(name=hn, port=port)
self.hosts[hn] = host
if len(tokens) > 1:
for t in tokens[1:]:
if t.startswith('#'):
break
try:
(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))
self.groups[active_group_name].add_host(host)
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
if line.startswith("[") and ":children]" in line:
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
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)
def _parse_group_variables(self):
group = None
for lineno in range(len(self.lines)):
line = self.lines[lineno].strip()
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, v) = [e.strip() for e in line.split("=", 1)]
group.set_variable(k, self._parse_value(v))
def get_host_variables(self, host):
return {}