4. Python API
想说ansible api这几年变化好大,而且非常不易用
4.1 官方示例解析
地址:https://docs.ansible.com/ansible/latest/dev_guide/developing_api.html
官方示例的是如何通过访问Inventory并执行对应的modules
把官方例子搬下来,然后看着文档做一些本地化修改
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Date: 2020-03-18
# @File: example-execute.py
# @Author: zhangwei
# @Desc: 官方例子解析
import json
import shutil
from ansible.module_utils.common.collections import ImmutableDict
from ansible.parsing.dataloader import DataLoader
from ansible.vars.manager import VariableManager
from ansible.inventory.manager import InventoryManager
from ansible.playbook.play import Play
from ansible.executor.task_queue_manager import TaskQueueManager
from ansible.plugins.callback import CallbackBase
from ansible import context
import ansible.constants as C
class ResultCallback(CallbackBase):
def __init__(self):
super().__init__()
self.hosts_ok = []
self.hosts_failed = []
self.hosts_unreachable = []
self.hosts_all = {}
def v2_runner_on_ok(self, result, **kwargs):
self.hosts_ok.append({result._host.name: result._result})
self.hosts_all[result._host.name] = result._result
def v2_runner_on_failed(self, result, **kwargs):
self.hosts_failed.append({result._host.name: result._result})
self.hosts_all[result._host.name] = result._result
def v2_runner_on_unreachable(self, result, **kwargs):
self.hosts_unreachable.append({result._host.name: result._result})
self.hosts_all[result._host.name] = result._result
context.CLIARGS = ImmutableDict(
connection='local', module_path=['/to/mymodules'], forks=10, become=None,
become_method=None, become_user=None, check=False, diff=False)
loader = DataLoader()
passwords = dict(vault_pass='secret')
results_callback = ResultCallback()
inventory = InventoryManager(
loader=loader, sources='/app/ansible/ansible-etc/inventory_dynamic_example.py'
)
variable_manager = VariableManager(loader=loader, inventory=inventory)
play_source = dict(
name = "Ansible Play",
hosts = 'test_01',
gather_facts = 'no',
tasks = [
dict(
action=dict(module='shell', args='hostname'),
register='shell_out'
)
]
)
play = Play().load(
play_source, variable_manager=variable_manager, loader=loader
)
tqm = None
try:
tqm = TaskQueueManager(
inventory=inventory,
variable_manager=variable_manager,
loader=loader,
passwords=passwords,
stdout_callback=results_callback,
)
result = tqm.run(play)
print(f'result: {result}')
for ip, ret in results_callback.hosts_all.items():
output = [
ip,
ret['stdout'] if 'stdout' in ret else '',
ret['exception'] if 'exception' in ret else '',
]
print(','.join(output))
finally:
if tqm is not None:
tqm.cleanup()
shutil.rmtree(C.DEFAULT_LOCAL_TMP, True)
然后执行,我test_01下有两台独立节点,inventory数据为
inventory = {
"all": {
"hosts": [],
"vars": {
"ansible_ssh_user": "root",
"ansible_ssh_port": 22
},
"children": []
},
"test_01": {
"hosts": [
"10.3.10.40"
],
"vars": {
"ansible_ssh_pass": "GIMDvZcgqhaYhWk9"
},
"children": ["test_01_01"]
},
"test_01_01": {
"hosts": [
"10.3.10.154"
],
"vars": {},
"children": []
},
"_meta": {}
}
执行我们的脚本,结果不对劲,两台节点主机名竟然一样?
(py36) [root@VM_10_40_centos test-scripts]# ./example-execute.py
result: 0
10.3.10.40,VM_10_40_centos,
10.3.10.154,VM_10_40_centos,
原来是 connection 参数设置为 local,只管理本地主机,修改一下代码片段为
context.CLIARGS = ImmutableDict(connection='smart',)
重新执行报错,错误提示很明显
ansible/plugins/connection/ssh.py,line 584,self._play_context.verbosity 未定义
(py36) [root@VM_10_40_centos test-scripts]# ./example-execute.py
result: 2
10.3.10.40,,Traceback (most recent call last):
File "/app/ansible/py36/lib64/python3.6/site-packages/ansible/executor/task_executor.py", line 146, in run
res = self._execute()
File "/app/ansible/py36/lib64/python3.6/site-packages/ansible/executor/task_executor.py", line 645, in _execute
result = self._handler.run(task_vars=variables)
File "/app/ansible/py36/lib64/python3.6/site-packages/ansible/plugins/action/shell.py", line 27, in run
result = command_action.run(task_vars=task_vars)
File "/app/ansible/py36/lib64/python3.6/site-packages/ansible/plugins/action/command.py", line 24, in run
results = merge_hash(results, self._execute_module(task_vars=task_vars, wrap_async=wrap_async))
File "/app/ansible/py36/lib64/python3.6/site-packages/ansible/plugins/action/__init__.py", line 780, in _execute_module
self._make_tmp_path()
File "/app/ansible/py36/lib64/python3.6/site-packages/ansible/plugins/action/__init__.py", line 348, in _make_tmp_path
result = self._low_level_execute_command(cmd, sudoable=False)
File "/app/ansible/py36/lib64/python3.6/site-packages/ansible/plugins/action/__init__.py", line 1071, in _low_level_execute_command
rc, stdout, stderr = self._connection.exec_command(cmd, in_data=in_data, sudoable=sudoable)
File "/app/ansible/py36/lib64/python3.6/site-packages/ansible/plugins/connection/ssh.py", line 1191, in exec_command
cmd = self._build_command(*args)
File "/app/ansible/py36/lib64/python3.6/site-packages/ansible/plugins/connection/ssh.py", line 584, in _build_command
if self._play_context.verbosity > 3:
TypeError: '>' not supported between instances of 'NoneType' and 'int'
搜了一下这块的定义:https://docs.ansible.com/ansible/latest/modules/debug_module.html
verbosity, default 0, 设置为3时加上 -vvv 参数做 ssh debug
奇怪的是,文档说是有default参数,ansible命令帮助里也说时有默认值的,我们还是看一下
最直接的 debug 当然是 print 一下,在 ssh.py 内加入两个调试片段
print('zw', self._play_context)
print('zw', type(self._play_context))
重新执行脚本,拿到我们的信息
(py36) [root@VM_10_40_centos test-scripts]# ./example-execute.py
zw <ansible.playbook.play_context.PlayContext object at 0x7f0ee4056d30>
zw <class 'ansible.playbook.play_context.PlayContext'>
重新追到 ansible/playbook/play_context.py,找到 class PlayContext
PlayContext 就是存储执行参数的对象,发现了设置变量的代码
class PlayContext(Base):
...
def set_attributes_from_cli(self):
self.verbosity = context.CLIARGS.get('verbosity') # Else default
这里也被注释了 Else default,但为什么是 NoneType呢。还是我call的方法不对,不管了,我先补上
把自己的代码片段修改一下
context.CLIARGS = ImmutableDict(connection='smart', verbosity=0)
重新执行,结果正常
(py36) [root@VM_10_40_centos test-scripts]# ./example-execute.py
result: 0
10.3.10.154,VM_10_154_centos,
10.3.10.40,VM_10_40_centos,
好的,确认是为获取到默认值。我改一下 ansible 的原始代码再试试
编辑 ansible/playbook/play_context.py,把self.verbosity赋值那一行改为
self.verbosity = context.CLIARGS.get('verbosity', 0) # Else default
再把我们自己的代码改为
context.CLIARGS = ImmutableDict(connection='smart')
执行脚本测试,结果正常
(py36) [root@VM_10_40_centos test-scripts]# ./example-execute.py
result: 0
10.3.10.154,VM_10_154_centos,
10.3.10.40,VM_10_40_centos,
好了,提issue去...先搜一下有没有相关,发现一个应该也是国人老哥
https://github.com/ansible/ansible/issues/64933
我看这答复,意思是不管了?
The internal Python API is considered an unsupported aspect of Ansible and this
is not considered a bug unless there is an issue with an Ansible binary
(ansible, ansible-playbook, ansible-doc, etc) or an issue with an external
API such as are provided for the development of plugins (modules, dynamic
inventories, callbacks, strategies, etc).
搜了一下互联网上并没有多少关于这个情况记录,那我写一下吧...
4.2 执行playbook
埋个坑,后面更...