Ansble源碼解析 Inventory配置文件解析模塊ini.py

ini.py

做者:煮酒品茶

說明

ini.py是對配置文件進行解析的模塊,主要處理了組,主機,組的變量,子組等關係。一個inventory中飲含 groups hosts 這兩個很是重要的屬性,其中有兩個死的組all ungrouped組。nginx

看看源碼再理解他的配置就會很好理解,有點意思的地方是做者居然用索引號去取值,而不是傳統的for,有點搞。web

繼續分析

這裏的邏輯是shell

  1. 打開配置文件,但配置文件是寫死的
  2. 作基礎的解析,獲得all ungroupd組,並處理基本的主機主機變量組
  3. 處理子組,由於就是個對應關係
  4. 把深度爲0的組加入到all組中
  5. 解析組的變量

測試,看一下下面的原型再看這個

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 {}
相關文章
相關標籤/搜索