Python呼叫ansible API系列(五)綜合使用

昀溪發表於2019-04-10

如何把動態生成資產資訊、執行playbook以及自定義結果結合起來用呢?

#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""
透過ansible API動態生成ansible資產資訊但不產生實際的hosts檔案
主機資訊都可以透過資料庫獲得,然後生成指定格式,最後呼叫這個類來
生成主機資訊。
"""

import sys
# 用於讀取YAML和JSON格式的檔案
from ansible.executor.playbook_executor import PlaybookExecutor
from ansible.parsing.dataloader import DataLoader
# 用於儲存各類變數資訊
from ansible.vars.manager import VariableManager
# 用於匯入資產檔案
from ansible.inventory.manager import InventoryManager
# 操作單個主機資訊
from ansible.inventory.host import Host
# 操作單個主機組資訊
from ansible.inventory.group import Group
# 狀態回撥,各種成功失敗的狀態
from ansible.plugins.callback import CallbackBase
from collections import namedtuple


class PlaybookCallResultCollector(CallbackBase):
    """
    playbook的callback改寫,格式化輸出playbook執行結果
    """
    CALLBACK_VERSION = 2.0

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.task_ok = {}
        self.task_unreachable = {}
        self.task_failed = {}
        self.task_skipped = {}
        self.task_status = {}

    def v2_runner_on_unreachable(self, result):
        """
        重寫 unreachable 狀態
        :param result:  這是父類裡面一個物件,這個物件可以獲取執行任務資訊
        """
        self.task_unreachable[result._host.get_name()] = result

    def v2_runner_on_ok(self, result, *args, **kwargs):
        """
        重寫 ok 狀態
        :param result:
        """
        self.task_ok[result._host.get_name()] = result

    def v2_runner_on_failed(self, result, *args, **kwargs):
        """
        重寫 failed 狀態
        :param result:
        """
        self.task_failed[result._host.get_name()] = result

    def v2_runner_on_skipped(self, result):
        self.task_skipped[result._host.get_name()] = result

    # def v2_playbook_on_stats(self, stats):
    #     hosts = sorted(stats.processed.keys())
    #     for h in hosts:
    #         t = stats.summarize(h)
    #         self.task_status[h] = {
    #             "ok": t["ok"],
    #             "changed": t["changed"],
    #             "unreachable": t["unreachable"],
    #             "skipped": t["skipped"],
    #             "failed": t["failed"]
    #         }


class MyInventory:
    def __init__(self, hostsresource):
        """
        初始化函式
        :param hostsresource: 主機資源可以有2種形式
        列表形式: [{"ip": "172.16.48.171", "port": "22", "username": "root", "password": "123456"}]
        字典形式: {
                    "Group1": {
                        "hosts": [{"ip": "192.168.200.10", "port": "1314", "username": "root", "password": None}],
                        "vars": {"var1": "ansible"}
                    },
                    "Group2": {}
                }
        """
        self._hostsresource = hostsresource
        self._loader = DataLoader()
        self._hostsfilelist = ["temphosts"]
        """
        sources這個我們知道這裡是設定hosts檔案的地方,它可以是一個列表裡面包含多個檔案路徑且檔案真實存在,在單純的執行ad-hoc的時候這裡的
        檔案裡面必須具有有效的hosts配置,但是當透過動態生成的資產資訊的時候這個檔案必須存在但是它裡面可以是空的,如果這裡配置成None那麼
        它不影響資產資訊動態生成但是會有一個警告,所以還是要配置一個真實檔案。
        """
        self._inventory = InventoryManager(loader=self._loader, sources=self._hostsfilelist)
        self._variable_manager = VariableManager(loader=self._loader, inventory=self._inventory)

        self._dynamic_inventory()

    def _add_dynamic_group(self, hosts_list, groupname, groupvars=None):
        """
        動態新增主機到指定的主機組

        完整的HOSTS檔案格式
        [test1]
        hostname ansible_ssh_host=192.168.1.111 ansible_ssh_user="root" ansible_ssh_pass="123456"

        但通常我們都省略hostname,埠也省略因為預設是22,這個在ansible配置檔案中有,除非有非22埠的才會配置
        [test1]
        192.168.100.10 ansible_ssh_user="root" ansible_ssh_pass="123456" ansible_python_interpreter="/PATH/python3/bin/python3"

        :param hosts_list: 主機列表 [{"ip": "192.168.100.10", "port": "22", "username": "root", "password": None}, {}]
        :param groupname:  組名稱
        :param groupvars:  組變數,格式為字典
        :return:
        """
        # 新增組
        my_group = Group(name=groupname)
        self._inventory.add_group(groupname)

        # 新增組變數
        if groupvars:
            for key, value in groupvars.items():
                my_group.set_variable(key, value)

        # 新增一個主機
        for host in hosts_list:
            hostname = host.get("hostname", None)
            hostip = host.get("ip", None)
            if hostip is None:
                print("IP地址為空,跳過該元素。")
                continue
            hostport = host.get("port", "22")
            username = host.get("username", "root")
            password = host.get("password", None)
            ssh_key = host.get("ssh_key", None)
            python_interpreter = host.get("python_interpreter", None)

            try:
                # hostname可以不寫,如果為空預設就是IP地址
                if hostname is None:
                    hostname = hostip
                # 生成一個host物件
                my_host = Host(name=hostname, port=hostport)
                # 新增主機變數
                self._variable_manager.set_host_variable(host=my_host, varname="ansible_ssh_host", value=hostip)
                self._variable_manager.set_host_variable(host=my_host, varname="ansible_ssh_port", value=hostport)
                if password:
                    self._variable_manager.set_host_variable(host=my_host, varname="ansible_ssh_pass", value=password)
                self._variable_manager.set_host_variable(host=my_host, varname="ansible_ssh_user", value=username)
                if ssh_key:
                    self._variable_manager.set_host_variable(host=my_host, varname="ansible_ssh_private_key_file", value=ssh_key)
                if python_interpreter:
                    self._variable_manager.set_host_variable(host=my_host, varname="ansible_python_interpreter", value=python_interpreter)

                # 新增其他變數
                for key, value in host.items():
                    if key not in ["ip", "hostname", "port", "username", "password", "ssh_key", "python_interpreter"]:
                        self._variable_manager.set_host_variable(host=my_host, varname=key, value=value)

                # 新增主機到組
                self._inventory.add_host(host=hostname, group=groupname, port=hostport)

            except Exception as err:
                print(err)

    def _dynamic_inventory(self):
        """
        新增 hosts 到inventory
        :return:
        """
        if isinstance(self._hostsresource, list):
            self._add_dynamic_group(self._hostsresource, "default_group")
        elif isinstance(self._hostsresource, dict):
            for groupname, hosts_and_vars in self._hostsresource.items():
                self._add_dynamic_group(hosts_and_vars.get("hosts"), groupname, hosts_and_vars.get("vars"))

    @property
    def INVENTORY(self):
        """
        返回資產例項
        :return:
        """
        return self._inventory

    @property
    def VARIABLE_MANAGER(self):
        """
        返回變數管理器例項
        :return:
        """
        return self._variable_manager


class AnsibleRunner(object):
    def __init__(self, hostsresource):
        Options = namedtuple("Options", [
            "connection", "remote_user", "ask_sudo_pass", "verbosity", "ack_pass",
            "module_path", "forks", "become", "become_method", "become_user", "check",
            "listhosts", "listtasks", "listtags", "syntax", "sudo_user", "sudo", "diff"
        ])
        self._options = Options(connection='smart', remote_user=None, ack_pass=None, sudo_user=None, forks=5, sudo=None,
                          ask_sudo_pass=False,
                          verbosity=5, module_path=None, become=None, become_method=None, become_user=None, check=False,
                          diff=False,
                          listhosts=None, listtasks=None, listtags=None, syntax=None)
        self._passwords = dict(sshpass=None, becomepass=None)  # 這個可以為空,因為在hosts檔案中
        self._loader = DataLoader()
        myinven = MyInventory(hostsresource=hostsresource)
        self._inventory = myinven.INVENTORY
        self._variable_manager = myinven.VARIABLE_MANAGER

    def run_playbook(self, playbook_path, extra_vars=None):
        """
        執行playbook
        :param playbook_path: playbook的yaml檔案路徑
        :param extra_vars: 額外變數
        :return: 無返回值
        """
        try:
            if extra_vars:
                self._variable_manager.extra_vars = extra_vars
            playbook = PlaybookExecutor(playbooks=[playbook_path], inventory=self._inventory, variable_manager=self._variable_manager, loader=self._loader,
                                        options=self._options, passwords=self._passwords)
            # 配置使用自定義callback
            self._callback = PlaybookCallResultCollector()
            playbook._tqm._stdout_callback = self._callback
            # 執行playbook
            playbook.run()
        except Exception as err:
            print(err)

    def get_playbook_result(self):
        """
        獲取playbook執行結果
        :return:
        """
        result_raw = {"ok": {}, "failed": {}, "unreachable": {}, "skipped": {}, "status": {}}
        for host, result in self._callback.task_ok.items():
            result_raw["ok"][host] = result._result

        for host, result in self._callback.task_failed.items():
            result_raw["failed"][host] = result._result

        for host, result in self._callback.task_unreachable.items():
            result_raw["unreachable"][host] = result._result

        for host, result in self._callback.task_skipped.items():
            result_raw["skipped"][host] = result._result

        for host, result in self._callback.task_status.items():
            result_raw["status"][host] = result._result

        return result_raw


def main():
    temphosts_list = [{"ip": "172.16.48.250", "port": "22", "username": "root", "password": "12qwaszx!@QW"}]

    temphosts_dict = {
        "Group1": {
            "hosts": [{"ip": "192.168.200.10", "port": "1314", "username": "root", "password": None}],
            "vars": {"var1": "ansible"}
        },
        # "Group2": {}
    }

    # mi = MyInventory(temphosts_list)
    # for group, hosts in mi.INVENTORY.get_groups_dict().items():
    #     print(group, hosts)
    # host = mi.INVENTORY.get_host("192.168.200.10")
    # print(mi.VARIABLE_MANAGER.get_vars(host=host))

    ar = AnsibleRunner(temphosts_list)
    ar.run_playbook("/Users/rex.chen/PycharmProjects/IDCMigration/AnsibleStudy/f1.yml")
    print(ar.get_playbook_result())

if __name__ == "__main__":
    try:
        main()
    finally:
        sys.exit()
View Code

 

相關文章