ansible笔记4:Python API

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

埋个坑,后面更...

猜你喜欢

转载自www.cnblogs.com/tutuye/p/12533186.html
今日推荐