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

from .ansible_api import AnsibleClient
sources = '192.168.1.218'
client = AnsibleClient(sources) ...

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

[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),代码如下:

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中,代码片段如下所示:

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方法,代码片段如下:

    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这一步,接着查看这一方法的源代码,如下所示:

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分支,再次运行程序,报错信息变成了下面这样:

[WARNING]: Unable to parse 192.168.1.218 as an inventory source

  成功了一半了,但是为何还会出现报错信息,得接着查

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

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,代码如下:

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

  果然,如果想让返回结果为True的话,需要满足if条件,即文件不存在,并且 ',' 存在。

  这里将if not os.path.exists(b_path) and ',' in host_list:这一句修改为if not os.path.exists(b_path):,运行程序,报错没了。

  再回头捋一遍整个源码思路,当我们将IP地址传入ansible连接初始化时,会校验该IP地址是否为文件,如果是文件,那么就解析文件路径,最终看该文件内是否有目标机器。

  不为文件,就校验inventory plugin的各种插件(默认开启5个,其中包括host_list)。如果该IP符合插件所需特征,就由插件来解析IP地址,如果不符合,最终抛出报错信息Unable to parse %s as an inventory source。

  要想解析这个问题,有两个思路:

  第一个,最简单的就是传入IP地址为一个的时候,末尾加上 ','

  第二个,修改源码两个地方,一是跳过unfrackpath分支,二是让host_list.py的verify_file方法为真。上文均有过修改代码的描述。

  

  好了,整个分析结束。我猜,ansible的意思是,既然初始化ansible连接需要传入host的list,那么单个IP地址不能称为list,如果想成为list,就得加个 ',' ,哈哈

001_记一次ansible api二次开发遇到的小问题的更多相关文章

  1. Civil 3D API二次开发学习指南

    Civil 3D构建于AutoCAD 和 Map 3D之上,在学习Civil 3D API二次开发之前,您至少需要了解AutoCAD API的二次开发,你可以参考AutoCAD .NET API二次开 ...

  2. (50)zabbix API二次开发使用与介绍

    zabbix API开发库 zabbix API请求和响应都是json,并且还提供了各种语法的lib库,http://zabbix.org/wiki/Docs/api/libraries,包含php. ...

  3. 美客分销商城-接力购源码系统,全开源代码可进行二次开发,微信小程序分销商城

    1. 准备服务器.域名(SSL证书).认证的微信小程序.微信支付商户号 2. 系统功能简介 三.演示案例,微信扫码查看 四.后台管理系统 五. 全套开源源码,进行二次开发 六.本系统完美运营,全套代码 ...

  4. Autodesk View and Data API二次开发学习指南

    什么是View and Data API? 使用View and Data API,你可以轻松的在网页上显示大型三维模型或者二维图纸而不需要安装任何插件.通过View and Data API,你可以 ...

  5. Kettle api 二次开发之 日志的保存

    使用kettle做数据抽取的时候可以使用图形化的工具配置日志保存参数,选择数据库连接,输入日志表名称, 点击sql 执行对应的sql创建日志表即可. 点击保存之后,日志配置会保存在trans或者job ...

  6. Python调用ansible API系列(四)动态生成hosts文件

    方法一:通过最原始的操作文件的方式 #!/usr/bin/env python # -*- coding: utf-8 -*- """ 通过操作文件形式动态生成ansib ...

  7. Navisworks API 简单二次开发 (自定义工具条)

    在Navisworks软件运行的时候界面右侧有个工具条.比较方便.但是在二次开发的时候我不知道在Api那里调用.如果有网友知道请告诉我.谢谢. 我用就自己设置一个工具.界面比较丑!没有美工. 代码: ...

  8. 用JSON-server模拟REST API(二) 动态数据

    用JSON-server模拟REST API(二) 动态数据 上一篇演示了如何安装并运行 json server , 在这里将使用第三方库让模拟的数据更加丰满和实用. 目录: 使用动态数据 为什么选择 ...

  9. Express4.x API (二):Request (译)

    写在前面 最近学习express想要系统的过一遍API,www.expressjs.com是express英文官网(进入www.epxressjs.com.cn发现也是只有前几句话是中文呀~~),所以 ...

随机推荐

  1. python中的常用数据类型

    python中的常用数据类型 以下是个人总结的python中常见的数据类型,话不多说,我们直接步入正题: 数字类型 整型类:int类可以表示任意大小的整数值,在python中没有像JAVA或者C那样的 ...

  2. 数据可视化之powerBI基础(十五)Power BI同步切片器,你知道怎么用吗?

    https://zhuanlan.zhihu.com/p/67932754 在PowerBI报表中,切片器绝对是最常用的控件了,利用它可以进行各种维度的动态切换,同一个页面中的所有图表可以同步响应:利 ...

  3. 数据可视化之分析篇(九)PowerBI数据分析实践第三弹 | 趋势分析法

    https://zhuanlan.zhihu.com/p/133484654 以财务报表分析为例,介绍通用的分析方法论,整体架构如下图所示: (点击查看大图) 我会围绕这五种不同的方法论,逐步阐述他们 ...

  4. 为什么阿里、头条、美团这些互联网大厂都在用Spring Boot?

    前言 自 2014 年发布至今,Spring Boot 的搜索指数 一路飙升.没错 Spring Boot 越来越火了,作为一名行走一线的 Java 程序员,你可能在各个方面感受到了 Spring B ...

  5. 手写简易SpringMVC

    手写简易SpringMVC 手写系列框架代码基于普通Maven构建,因此在手写SpringMVC的过程中,需要手动的集成Tomcat容器 必备知识: Servlet相关理解和使用,Maven,Java ...

  6. Ethical Hacking - POST EXPLOITATION(3)

    Spying - Capturing Key Strikes & Taking Screen Shots Log all mouse/keyboard events > keyscan- ...

  7. stm32-HAL库串口收发

    串口发送 重写fputc函数 /* 优点 直接使用printf函数,发送数据长度无限制,不需要额外的数组空间 缺点 只能对应一个串口,暂时没想到解决方案 */ //头文件中要包含 stdio.h 然后 ...

  8. 何时/如何使用 Vue3 render 函数

    什么是 DOM? 如果我们把这个 HTML 加载到浏览器中,浏览器创建这些节点,用来显示网页.所以这个HTML映射到一系列DOM节点,然后我们可以使用JavaScript进行操作.例如: let it ...

  9. Salt 系统初始化

    目录 编辑states文件 1.DNS配置  dns.sls(在init目录下创建一个files文件,然后把resolv.conf放到文件下) [root@master init]# cat dns. ...

  10. Android蓝牙读取短信调研

    对“直接通过蓝牙来获取连接手机的短信信息”这个需求做了一些技术调研,如下是调研过程中的一些记录. 1.无法得到BluetoothMasClient类 在文章https://blog.csdn.net/ ...