记一次ansible api二次开发遇到的小问题

在某次关于发布系统的项目中,需要调用ansible来发布任务,其中一段代码是初始化ansible的连接,并传入一个source(目标机器)的值,代码段如下:

1
2
3
4
from .ansible_api import AnsibleClient
sources = ‘192.168.1.218’
client = AnsibleClient(sources)

  完成后发现一直报错,如下所示错误信息:

1
[WARNING]: Unable to parse python/devops/192.168.1.218 as an inventory source
  看字面意思描述,是说无法解析python/devops/192.168.1.218为一个inventory源。inventory是ansible专用的一个用来存储目标机器的文件。通过报错信息大概能够猜出,当我想要以IP地址的方式传入目标机器时候,ansible却把IP当做inventory文件来解析了。

先抛出结论,正确的传值方式,是在IP地址后增加一个 ‘,’ ,让sources = ‘192.168.1.218,’ ,再传入。或者传入两个以上的IP,如’192.168.1.218,192.168.1.219’ 。

但是传入一个IP是我的一个正常应用场景,那么通过源码分析,我们来看看为何要出现以上结果。

实例化的AnsibleClient是在ansible_api文件中,有一个AnsibleClient类,其中有初始化方法__init__(self, source),代码如下:

1
2
3
4
5
6
7
8
class AnsibleClient:
def init(self, source):
self.source = source
self.loader = DataLoader()
self.inventory = InventoryManager(loader=self.loader, sources=self.source)
self.variable_manager = VariableManager(loader=self.loader, inventory=self.inventory)
self.passwords = dict(vault_pass=‘secret’)
self.callback = None
  在以上代码中,可以看到在实例化AnsibleClient连接时,之前的192.168.1.218会由source参数传进来,并且最终再传入InventoryManager

接着再查看InventoryManager的源码,源码所在文件位置为路径ansible/inventory/manager.py中,代码片段如下所示:

1
2
3
4
5
6
7
8
9
class InventoryManager(object):
def init(self, loader, sources=None):
if sources is None:
self._sources = []
elif isinstance(sources, string_types):
self._sources = [sources]
else:
self._sources = sources
self.parse_sources(cache=True)
  通过以上代码,会发现在InventoryManager实例化时候,如果sources有值传入,那么sources会赋值给self._sources,并继续走self.parse_sources(cache=True)这段,通过利用print(‘报错信息在此!!!’)断点大法,确实如此。

接着查看parse_sources方法,代码片段如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
def parse_sources(self, cache=False):
‘’’ iterate over inventory sources and parse each one to populate it’’’

parsed = False
# allow for multiple inventory parsing
for source in self._sources:

    if source:
        if ',' not in source:
            source = unfrackpath(source, follow=False)
        parse = self.parse_source(source, cache=cache)
        if parse and not parsed:
            parsed = True

注意里面有一句if ‘,’ not in source:,此时我们会走到这一分支,并进入unfrackpath这一步,接着查看这一方法的源代码,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def unfrackpath(path, follow=True, basedir=None):
b_basedir = to_bytes(basedir, errors=‘surrogate_or_strict’, nonstring=‘passthru’)

if b_basedir is None:
    b_basedir = to_bytes(os.getcwd(), errors='surrogate_or_strict')
elif os.path.isfile(b_basedir):
    b_basedir = os.path.dirname(b_basedir)

b_final_path = os.path.expanduser(os.path.expandvars(to_bytes(path, errors='surrogate_or_strict')))

if not os.path.isabs(b_final_path):
    b_final_path = os.path.join(b_basedir, b_final_path)

if follow:
    b_final_path = os.path.realpath(b_final_path)

return to_text(os.path.normpath(b_final_path), errors='surrogate_or_strict')

这个方法是用来解析文件路径的,通过这个方法,让我们的’192.168.1.218’,最终变成了’python/devops/192.168.1.218’,此时,我们得到了报错信息提示源是谁了。但是我们传入的是192.168.1.218,不想被解析为文件,所以回到parse_sources方法,将if ‘,’ not in source变为if ‘,’ and ‘.’ not in source:,跳过if分支,再次运行程序,报错信息变成了下面这样:

1
[WARNING]: Unable to parse 192.168.1.218 as an inventory source
  成功了一半了,但是为何还会出现报错信息,得接着查

回到parse_sources方法接着往下,source通过跳过unfrackpathparse的if分支后,被传入self.parse_source(source, cache=cache)方法,该方法代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
def parse_source(self, source, cache=False):
‘’’ Generate or update inventory for the source provided ‘’’

    parsed = False
    display.debug(u'Examining possible inventory source: %s' % source)

    # use binary for path functions
    b_source = to_bytes(source)

    # process directories as a collection of inventories
    if os.path.isdir(b_source):
        display.debug(u'Searching for inventory files in directory: %s' % source)
        for i in sorted(os.listdir(b_source)):

            display.debug(u'Considering %s' % i)
            # Skip hidden files and stuff we explicitly ignore
            if IGNORED.search(i):
                continue

            # recursively deal with directory entries
            fullpath = to_text(os.path.join(b_source, i), errors='surrogate_or_strict')
            parsed_this_one = self.parse_source(fullpath, cache=cache)
            display.debug(u'parsed %s as %s' % (fullpath, parsed_this_one))
            if not parsed:
                parsed = parsed_this_one
    else:
        # left with strings or files, let plugins figure it out

        # set so new hosts can use for inventory_file/dir vars
        self._inventory.current_source = source

        # try source with each plugin
        failures = []
        for plugin in self._fetch_inventory_plugins():

            plugin_name = to_text(getattr(plugin, '_load_name', getattr(plugin, '_original_path', '')))
            display.debug(u'Attempting to use plugin %s (%s)' % (plugin_name, plugin._original_path))

            # initialize and figure out if plugin wants to attempt parsing this file
            try:
                plugin_wants = bool(plugin.verify_file(source))
                print(plugin_wants,source)
            except Exception:
                plugin_wants = False

            if plugin_wants:
                try:
                    # FIXME in case plugin fails 1/2 way we have partial inventory
                    plugin.parse(self._inventory, self._loader, source, cache=cache)
                    try:
                        plugin.update_cache_if_changed()
                    except AttributeError:
                        # some plugins might not implement caching
                        pass
                    parsed = True
                    display.vvv('Parsed %s inventory source with %s plugin' % (source, plugin_name))
                    break
                except AnsibleParserError as e:
                    display.debug('%s was not parsable by %s' % (source, plugin_name))
                    tb = ''.join(traceback.format_tb(sys.exc_info()[2]))
                    failures.append({'src': source, 'plugin': plugin_name, 'exc': e, 'tb': tb})
                except Exception as e:
                    display.debug('%s failed while attempting to parse %s' % (plugin_name, source))
                    tb = ''.join(traceback.format_tb(sys.exc_info()[2]))
                    failures.append({'src': source, 'plugin': plugin_name, 'exc': AnsibleError(e), 'tb': tb})
            else:
                display.vvv("%s declined parsing %s as it did not pass its verify_file() method" % (plugin_name, source))
        else:
            if not parsed and failures:
                # only if no plugin processed files should we show errors.
                for fail in failures:
                    display.warning(u'\n* Failed to parse %s with %s plugin: %s' % (to_text(fail['src']), fail['plugin'], to_text(fail['exc'])))
                    if 'tb' in fail:
                        display.vvv(to_text(fail['tb']))
                if C.INVENTORY_ANY_UNPARSED_IS_FAILED:
                    raise AnsibleError(u'Completely failed to parse inventory source %s' % (source))
    if not parsed:
        if source != '/etc/ansible/hosts' or os.path.exists(source):
            # only warn if NOT using the default and if using it, only if the file is present
            display.warning("Unable to parse %s as an inventory source" % source)

    # clear up, jic
    self._inventory.current_source = None

    return parsed

该方法比较长,但是可以看到里面的报错信息出处,在这里"Unable to parse %s as an inventory source",是因为if not parsed条件进来的,那么parsed为何False,还得看这段代码动作。

首先最开始parsed被置为了False,然后通过print大法得知if os.path.isdir(b_source)分支没有进入,走的是后面else分支。通过代码各种变量命名规则,大概能够猜到这里面是在做inventory plugin的判断。

如果parsed被置为了False,那么一定会报错,为了避免就一定会在这段代码里parsed会有置为True的地方,那么plugin_wants = bool(plugin.verify_file(source))这一句就至关重要。

通过plugin.verify_file(source)接收到参数source(192.168.1.218)后,parsed被置为了False,通过方法字面意思知道verify_file是校验source是否为文件的。我们查看verify_file的源代码,文件位置为ansible/plugins/invenroy/host_list.py,代码如下:

1
2
3
4
5
6
7
def verify_file(self, host_list):

    valid = False
    b_path = to_bytes(host_list, errors='surrogate_or_strict')
    if not os.path.exists(b_path) and ',' in host_list:
        valid = True
    return valid
    Absorbing material: www.goodsmaterial.com
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值