python3.8 调用ansible 2.9.4 api


1.安装ansible相关模块

pip3 install ansible_runner
pip3 install ansible_inventory
pip3 install ansible_playbook

2、重写官方的回调函数并根据官方的example 封装一个类

ansible2.py

import json
import shutil
from ansible.module_utils.common.collections import ImmutableDict  # 用于添加选项。比如: 指定远程用户remote_user=None
from ansible.parsing.dataloader import DataLoader  # 读取 json/yml/ini 格式的文件的数据解析器
from ansible.vars.manager import VariableManager  # 管理主机和主机组的变量管理器
from ansible.inventory.manager import InventoryManager  # 管理资源库的,可以指定一个 inventory 文件等
from ansible.playbook.play import Play  # 用于执行 Ad-hoc 的类 ,需要传入相应的参数
from ansible.executor.task_queue_manager import TaskQueueManager  # ansible 底层用到的任务队列管理器
from ansible.plugins.callback import CallbackBase  # 处理任务执行后返回的状态
from ansible import context  # 上下文管理器,他就是用来接收 ImmutableDict 的示例对象
import ansible.constants as C  # 用于获取 ansible 产生的临时文档


class ResultCallback(CallbackBase):
    """
    重写callbackBase类的部分方法
    """

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.host_ok = {}
        self.host_unreachable = {}
        self.host_failed = {}
        self.task_ok = {}

    def v2_runner_on_unreachable(self, result):
        self.host_unreachable[result._host.get_name()] = result

    def v2_runner_on_ok(self, result, **kwargs):
        self.host_ok[result._host.get_name()] = result

    def v2_runner_on_failed(self, result, **kwargs):
        self.host_failed[result._host.get_name()] = result


class MyAnsiable2(object):

    def __init__(self,
                 connection='local',  # 连接方式 local 本地方式,smart ssh方式
                 remote_user=None,  # ssh 用户
                 remote_password=None,  # ssh 用户的密码,应该是一个字典, key 必须是 conn_pass
                 private_key_file=None,  # 指定自定义的私钥地址
                 sudo=None, sudo_user=None, ask_sudo_pass=None,
                 module_path=None,  # 模块路径,可以指定一个自定义模块的路径
                 become=None,  # 是否提权
                 become_method=None,  # 提权方式 默认 sudo 可以是 su
                 become_user=None,  # 提权后,要成为的用户,并非登录用户
                 check=False, diff=False,
                 listhosts=None, listtasks=None, listtags=None,
                 verbosity=3,
                 syntax=None,
                 start_at_task=None,
                 inventory=None):

        # 函数文档注释
        """
        初始化函数,定义的默认的选项值,
        在初始化的时候可以传参,以便覆盖默认选项的值
        """
        context.CLIARGS = ImmutableDict(
            connection=connection,
            remote_user=remote_user,
            private_key_file=private_key_file,
            sudo=sudo,
            sudo_user=sudo_user,
            ask_sudo_pass=ask_sudo_pass,
            module_path=module_path,
            become=become,
            become_method=become_method,
            become_user=become_user,
            verbosity=verbosity,
            listhosts=listhosts,
            listtasks=listtasks,
            listtags=listtags,
            syntax=syntax,
            start_at_task=start_at_task,
        )
        # 三元表达式,假如没有传递 inventory, 就使用 "localhost,"
        # 指定 inventory 文件
        # inventory 的值可以是一个 资产清单文件
        # 也可以是一个包含主机的元组,这个仅仅适用于测试
        #  比如 : 1.1.1.1,    # 如果只有一个 IP 最后必须有英文的逗号
        #  或者: 1.1.1.1, 2.2.2.2
        self.inventory = inventory if inventory else "localhost,"
        # 实例化数据解析器
        self.loader = DataLoader()
        # 实例化 资产配置对象
        self.inv_obj = InventoryManager(loader=self.loader, sources=self.inventory)
        # 设置密码
        self.passwords = remote_password
        # 实例化回调插件对象
        self.results_callback = ResultCallback()
        # 变量管理器
        self.variable_manager = VariableManager(self.loader, self.inv_obj)

    def run(self, hosts='localhost', gather_facts="no", module="ping", args='', task_time=0):

        """
        参数说明:
        task_time -- 执行异步任务时等待的秒数,这个需要大于 0 ,等于 0 的时候不支持异步(默认值)。这个值应该等于执行任务实际耗时时间为好
        """
        play_source = dict(
            name="Ad-hoc",
            hosts=hosts,
            gather_facts=gather_facts,
            tasks=[
                # 这里每个 task 就是这个列表中的一个元素,格式是嵌套的字典
                # 也可以作为参数传递过来,这里就简单化了。
                {"action": {"module": module, "args": args}, "async": task_time, "poll": 0}])
        play = Play().load(play_source, variable_manager=self.variable_manager, loader=self.loader)
        tqm = None
        try:
            tqm = TaskQueueManager(
                inventory=self.inv_obj,
                variable_manager=self.variable_manager,
                loader=self.loader,
                passwords=self.passwords,
                stdout_callback=self.results_callback)
            result = tqm.run(play)

        finally:
            if tqm is not None:
                tqm.cleanup()
                shutil.rmtree(C.DEFAULT_LOCAL_TMP, True)

    def playbook(self, playbooks):

        """
        Keyword arguments:
        playbooks --  需要是一个列表类型
        """
        from ansible.executor.playbook_executor import PlaybookExecutor

        playbook = PlaybookExecutor(playbooks=playbooks,
                                    inventory=self.inv_obj,
                                    variable_manager=self.variable_manager,
                                    loader=self.loader,
                                    passwords=self.passwords)
        # 使用回调函数
        playbook._tqm._stdout_callback = self.results_callback
        result = playbook.run()

    def get_result(self):
        result_raw = {'success': {}, 'failed': {}, 'unreachable': {}}
        # print(self.results_callback.host_ok)
        for host, result in self.results_callback.host_ok.items():
            result_raw['success'][host] = result._result
        for host, result in self.results_callback.host_failed.items():
            result_raw['failed'][host] = result._result
        for host, result in self.results_callback.host_unreachable.items():
            result_raw['unreachable'][host] = result._result
        # 最终打印结果,并且使用 JSON 继续格式化
        print(json.dumps(result_raw, indent=4))
        return json.dumps(result_raw)

编写一个使用脚本 ansible_run_wc.py

from ansible2 import *  # 引用修改过的ansible2.py 的所有模块
import json

ansible3 = MyAnsiable2(inventory='/etc/ansible/hosts', connection='smart')  # 创建资源库对象
ansible3.run(hosts="192.168.28.108", module="shell", args='ls /root/')
stdout_dict = json.loads(ansible3.get_result())
print(stdout_dict, type(stdout_dict))
print(stdout_dict['success']['192.168.28.108']['stdout'], '######wc')

正常返回结果

{
    "success": {
        "192.168.28.108": {
            "changed": true,
            "end": "2020-07-10 06:09:25.644699",
            "stdout": "anaconda-ks.cfg\napache-activemq-5.15.10\napache-activemq-5.15.10-bin.tar.gz.tar.gz.tar.gz\nCDN_NODE.txt\ndns.txt\nmongodb-org-server-4.0.6-1.el7.x86_64.rpm\nnginx-1.8.1\nnginx-1.8.1.tar.gz\nnginx-1.8.1.tar.gz.gpg\nnode-v8.15.1.tar.gz\nossftp-1.0.3-linux-mac\nossftp-1.0.3-linux-mac.zip\npip-9.0.1.tar.gz\nPython-3.6.4\nPython-3.6.4.tgz\nredis-5.0.3.tar.gz",
            "cmd": "ls /root/",
            "rc": 0,
            "start": "2020-07-10 06:09:23.904708",
            "stderr": "",
            "delta": "0:00:01.739991",
            "invocation": {
                "module_args": {
                    "creates": null,
                    "executable": null,
                    "_uses_shell": true,
                    "strip_empty_ends": true,
                    "_raw_params": "ls /root/",
                    "removes": null,
                    "argv": null,
                    "warn": true,
                    "chdir": null,
                    "stdin_add_newline": true,
                    "stdin": null
                }
            },
            "stdout_lines": [
                "anaconda-ks.cfg",
                "apache-activemq-5.15.10",
                "apache-activemq-5.15.10-bin.tar.gz.tar.gz.tar.gz",
                "CDN_NODE.txt",
                "dns.txt",
                "mongodb-org-server-4.0.6-1.el7.x86_64.rpm",
                "nginx-1.8.1",
                "nginx-1.8.1.tar.gz",
                "nginx-1.8.1.tar.gz.gpg",
                "node-v8.15.1.tar.gz",
                "ossftp-1.0.3-linux-mac",
                "ossftp-1.0.3-linux-mac.zip",
                "pip-9.0.1.tar.gz",
                "Python-3.6.4",
                "Python-3.6.4.tgz",
                "redis-5.0.3.tar.gz"
            ],
            "stderr_lines": [],
            "ansible_facts": {
                "discovered_interpreter_python": "/usr/bin/python"
            },
            "_ansible_no_log": false
        }
    },
    "failed": {},
    "unreachable": {}
}
{'success': {'192.168.28.108': {'changed': True, 'end': '2020-07-10 06:09:25.644699', 'stdout': 'anaconda-ks.cfg', 'cmd': 'ls /root/', 'rc': 0, 'start': '2020-07-10 06:09:23.904708', 'stderr': '', 'delta': '0:00:01.739991', 'invocation': {'module_args': {'creates': None, 'executable': None, '_uses_shell': True, 'strip_empty_ends': True, '_raw_params': 'ls /root/', 'removes': None, 'argv': None, 'warn': True, 'chdir': None, 'stdin_add_newline': True, 'stdin': None}}, 'stdout_lines': ['anaconda-ks.cfg', 'apache-activemq-5.15.10', 'apache-activemq-5.15.10-bin.tar.gz.tar.gz.tar.gz', 'CDN_NODE.txt', 'dns.txt', 'mongodb-org-server-4.0.6-1.el7.x86_64.rpm', 'nginx-1.8.1', 'nginx-1.8.1.tar.gz', 'nginx-1.8.1.tar.gz.gpg', 'node-v8.15.1.tar.gz', 'ossftp-1.0.3-linux-mac', 'ossftp-1.0.3-linux-mac.zip', 'pip-9.0.1.tar.gz', 'Python-3.6.4', 'Python-3.6.4.tgz', 'redis-5.0.3.tar.gz'], 'stderr_lines': [], 'ansible_facts': {'discovered_interpreter_python': '/usr/bin/python'}, '_ansible_no_log': False}}, 'failed': {}, 'unreachable': {}} <class 'dict'>
anaconda-ks.cfg
CDN_NODE.txt
dns.txt ######wc

Process finished with exit code 0

连不上的错误结果

{
    "success": {},
    "failed": {},
    "unreachable": {
        "192.168.28.108": {
            "unreachable": true,
            "msg": "Failed to connect to the host via ssh: ssh: connect to host 192.168.28.108 port 22: Operation timed out",
            "changed": false
        }
    }
}
{'success': {}, 'failed': {}, 'unreachable': {'192.168.28.108': {'unreachable': True, 'msg': 'Failed to connect to the host via ssh: ssh: connect to host 192.168.28.108 port 22: Operation timed out', 'changed': False}}} <class 'dict'>
Traceback (most recent call last):
  File "/Users/mac/Desktop/Code/cc.ylwl.cn/cc/common/ansible_run_wc.py", line 8, in <module>
    print(stdout_dict['success']['192.168.28.108']['stdout'], '######wc')
KeyError: '192.168.28.108'

编写一个ansible playbook 测试脚本 ansible_playbook_test.py

from ansible2 import *
import json

ansible3 = MyAnsiable2(inventory='/etc/ansible/hosts', connection='smart')
ansible3.playbook(playbooks=['common/test.yml'])
stdout_dict = json.loads(ansible3.get_result())
print(stdout_dict, type(stdout_dict))
# print(stdout_dict['success']['192.168.28.48']['stdout'])

test.yml

- hosts: web
  gather_facts: F   #开启debug
  tasks:
  - name: nslookup
    shell: nslookup www.baidu.com
    ignore_errors: True
    register: tomcat_out  #定义变量存储返回的结果

执行结果

{
    "success": {
        "192.168.28.48": {
            "changed": true,
            "end": "2020-07-10 06:28:52.653587",
            "stdout": "Server:\t\t114.114.114.114\nAddress:\t114.114.114.114#53\n\nNon-authoritative answer:\nwww.baidu.com\tcanonical name = www.a.shifen.com.\nName:\twww.a.shifen.com\nAddress: 14.215.177.38\nName:\twww.a.shifen.com\nAddress: 14.215.177.39",
            "cmd": "nslookup www.baidu.com",
            "rc": 0,
            "start": "2020-07-10 06:28:52.529811",
            "stderr": "",
            "delta": "0:00:00.123776",
            "invocation": {
                "module_args": {
                    "creates": null,
                    "executable": null,
                    "_uses_shell": true,
                    "strip_empty_ends": true,
                    "_raw_params": "nslookup www.baidu.com",
                    "removes": null,
                    "argv": null,
                    "warn": true,
                    "chdir": null,
                    "stdin_add_newline": true,
                    "stdin": null
                }
            },
            "stdout_lines": [
                "Server:\t\t114.114.114.114",
                "Address:\t114.114.114.114#53",
                "",
                "Non-authoritative answer:",
                "www.baidu.com\tcanonical name = www.a.shifen.com.",
                "Name:\twww.a.shifen.com",
                "Address: 14.215.177.38",
                "Name:\twww.a.shifen.com",
                "Address: 14.215.177.39"
            ],
            "stderr_lines": [],
            "ansible_facts": {
                "discovered_interpreter_python": "/usr/bin/python"
            },
            "_ansible_no_log": false
        },
        "192.168.28.47": {
            "changed": true,
            "end": "2020-07-10 06:28:52.911008",
            "stdout": "Server:\t\t114.114.114.114\nAddress:\t114.114.114.114#53\n\nNon-authoritative answer:\nwww.baidu.com\tcanonical name = www.a.shifen.com.\nName:\twww.a.shifen.com\nAddress: 14.215.177.39\nName:\twww.a.shifen.com\nAddress: 14.215.177.38",
            "cmd": "nslookup www.baidu.com",
            "rc": 0,
            "start": "2020-07-10 06:28:52.748820",
            "stderr": "",
            "delta": "0:00:00.162188",
            "invocation": {
                "module_args": {
                    "creates": null,
                    "executable": null,
                    "_uses_shell": true,
                    "strip_empty_ends": true,
                    "_raw_params": "nslookup www.baidu.com",
                    "removes": null,
                    "argv": null,
                    "warn": true,
                    "chdir": null,
                    "stdin_add_newline": true,
                    "stdin": null
                }
            },
            "stdout_lines": [
                "Server:\t\t114.114.114.114",
                "Address:\t114.114.114.114#53",
                "",
                "Non-authoritative answer:",
                "www.baidu.com\tcanonical name = www.a.shifen.com.",
                "Name:\twww.a.shifen.com",
                "Address: 14.215.177.39",
                "Name:\twww.a.shifen.com",
                "Address: 14.215.177.38"
            ],
            "stderr_lines": [],
            "ansible_facts": {
                "discovered_interpreter_python": "/usr/bin/python"
            },
            "_ansible_no_log": false
        },
        "192.168.28.42": {
            "changed": true,
            "end": "2020-07-10 06:28:54.287579",
            "stdout": "Server:\t\t114.114.114.114\nAddress:\t114.114.114.114#53\n\nNon-authoritative answer:\nwww.baidu.com\tcanonical name = www.a.shifen.com.\nName:\twww.a.shifen.com\nAddress: 14.215.177.38\nName:\twww.a.shifen.com\nAddress: 14.215.177.39",
            "cmd": "nslookup www.baidu.com",
            "rc": 0,
            "start": "2020-07-10 06:28:54.131727",
            "stderr": "",
            "delta": "0:00:00.155852",
            "invocation": {
                "module_args": {
                    "creates": null,
                    "executable": null,
                    "_uses_shell": true,
                    "strip_empty_ends": true,
                    "_raw_params": "nslookup www.baidu.com",
                    "removes": null,
                    "argv": null,
                    "warn": true,
                    "chdir": null,
                    "stdin_add_newline": true,
                    "stdin": null
                }
            },
            "stdout_lines": [
                "Server:\t\t114.114.114.114",
                "Address:\t114.114.114.114#53",
                "",
                "Non-authoritative answer:",
                "www.baidu.com\tcanonical name = www.a.shifen.com.",
                "Name:\twww.a.shifen.com",
                "Address: 14.215.177.38",
                "Name:\twww.a.shifen.com",
                "Address: 14.215.177.39"
            ],
            "stderr_lines": [],
            "ansible_facts": {
                "discovered_interpreter_python": "/usr/bin/python"
            },
            "_ansible_no_log": false
        }
    },
    "failed": {},
    "unreachable": {}
}
{'success': {'192.168.28.48': {'changed': True, 'end': '2020-07-10 06:28:52.653587', 'stdout': 'Server:\t\t114.114.114.114\nAddress:\t114.114.114.114#53\n\nNon-authoritative answer:\nwww.baidu.com\tcanonical name = www.a.shifen.com.\nName:\twww.a.shifen.com\nAddress: 14.215.177.38\nName:\twww.a.shifen.com\nAddress: 14.215.177.39', 'cmd': 'nslookup www.baidu.com', 'rc': 0, 'start': '2020-07-10 06:28:52.529811', 'stderr': '', 'delta': '0:00:00.123776', 'invocation': {'module_args': {'creates': None, 'executable': None, '_uses_shell': True, 'strip_empty_ends': True, '_raw_params': 'nslookup www.baidu.com', 'removes': None, 'argv': None, 'warn': True, 'chdir': None, 'stdin_add_newline': True, 'stdin': None}}, 'stdout_lines': ['Server:\t\t114.114.114.114', 'Address:\t114.114.114.114#53', '', 'Non-authoritative answer:', 'www.baidu.com\tcanonical name = www.a.shifen.com.', 'Name:\twww.a.shifen.com', 'Address: 14.215.177.38', 'Name:\twww.a.shifen.com', 'Address: 14.215.177.39'], 'stderr_lines': [], 'ansible_facts': {'discovered_interpreter_python': '/usr/bin/python'}, '_ansible_no_log': False}, '192.168.28.47': {'changed': True, 'end': '2020-07-10 06:28:52.911008', 'stdout': 'Server:\t\t114.114.114.114\nAddress:\t114.114.114.114#53\n\nNon-authoritative answer:\nwww.baidu.com\tcanonical name = www.a.shifen.com.\nName:\twww.a.shifen.com\nAddress: 14.215.177.39\nName:\twww.a.shifen.com\nAddress: 14.215.177.38', 'cmd': 'nslookup www.baidu.com', 'rc': 0, 'start': '2020-07-10 06:28:52.748820', 'stderr': '', 'delta': '0:00:00.162188', 'invocation': {'module_args': {'creates': None, 'executable': None, '_uses_shell': True, 'strip_empty_ends': True, '_raw_params': 'nslookup www.baidu.com', 'removes': None, 'argv': None, 'warn': True, 'chdir': None, 'stdin_add_newline': True, 'stdin': None}}, 'stdout_lines': ['Server:\t\t114.114.114.114', 'Address:\t114.114.114.114#53', '', 'Non-authoritative answer:', 'www.baidu.com\tcanonical name = www.a.shifen.com.', 'Name:\twww.a.shifen.com', 'Address: 14.215.177.39', 'Name:\twww.a.shifen.com', 'Address: 14.215.177.38'], 'stderr_lines': [], 'ansible_facts': {'discovered_interpreter_python': '/usr/bin/python'}, '_ansible_no_log': False}, '192.168.28.42': {'changed': True, 'end': '2020-07-10 06:28:54.287579', 'stdout': 'Server:\t\t114.114.114.114\nAddress:\t114.114.114.114#53\n\nNon-authoritative answer:\nwww.baidu.com\tcanonical name = www.a.shifen.com.\nName:\twww.a.shifen.com\nAddress: 14.215.177.38\nName:\twww.a.shifen.com\nAddress: 14.215.177.39', 'cmd': 'nslookup www.baidu.com', 'rc': 0, 'start': '2020-07-10 06:28:54.131727', 'stderr': '', 'delta': '0:00:00.155852', 'invocation': {'module_args': {'creates': None, 'executable': None, '_uses_shell': True, 'strip_empty_ends': True, '_raw_params': 'nslookup www.baidu.com', 'removes': None, 'argv': None, 'warn': True, 'chdir': None, 'stdin_add_newline': True, 'stdin': None}}, 'stdout_lines': ['Server:\t\t114.114.114.114', 'Address:\t114.114.114.114#53', '', 'Non-authoritative answer:', 'www.baidu.com\tcanonical name = www.a.shifen.com.', 'Name:\twww.a.shifen.com', 'Address: 14.215.177.38', 'Name:\twww.a.shifen.com', 'Address: 14.215.177.39'], 'stderr_lines': [], 'ansible_facts': {'discovered_interpreter_python': '/usr/bin/python'}, '_ansible_no_log': False}}, 'failed': {}, 'unreachable': {}} <class 'dict'>
Server:     114.114.114.114
Address:    114.114.114.114#53

Non-authoritative answer:
www.baidu.com   canonical name = www.a.shifen.com.
Name:   www.a.shifen.com
Address: 14.215.177.38
Name:   www.a.shifen.com
Address: 14.215.177.39