ansible笔记4:Python API

4. Python API

api文档地址:https://docs.ansible.com/ansible/latest/dev_guide/developing_api.html

4.1 官方态度

ansible api这几年变化好大,而且非常不易用。

这回仔细看了一下文档提示部分,才发现”不用读系列“还是要读的...

This API is intended for internal Ansible use. Ansible may make changes to this API at any time that could break backward compatibility with older versions of the API. Because of this, external use is not supported by Ansible.

重点:

  • API是内部用的
  • 随时变化,不保证向后兼容性
  • 你们外面人用不保证质量

我建议有项目做的话,还是调用 命令 解决吧。

4.2 官方示例阅读

官方示例的是如何通过访问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).

搜了一下互联网上并没有多少关于这个情况记录,那我写一下吧...

原文地址:https://www.cnblogs.com/tutuye/p/12533186.html