05.Ansible Python API

Ansible Python API

官方文檔

參考

  • 官方示例
#!/usr/bin/env python

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):
    """A sample callback plugin used for performing an action as results come in

    If you want to collect all results into a single object for processing at
    the end of the execution, look into utilizing the ``json`` callback plugin
    or writing your own custom callback plugin
    """
    def v2_runner_on_ok(self, result, **kwargs):
        """Print a json representation of the result

        This method could store the result in an instance attribute for retrieval later
        """
        host = result._host
        print(json.dumps({host.name: result._result}, indent=4))

# since the API is constructed for CLI it expects certain options to always be set in the context object
context.CLIARGS = ImmutableDict(connection='local', module_path=['/to/mymodules'], forks=10, become=None,
                                become_method=None, become_user=None, check=False, diff=False)

# initialize needed objects
loader = DataLoader() # Takes care of finding and reading yaml, json and ini files
passwords = dict(vault_pass='secret')

# Instantiate our ResultCallback for handling results as they come in. Ansible expects this to be one of its main display outlets
results_callback = ResultCallback()

# create inventory, use path to host config file as source or hosts in a comma separated string
inventory = InventoryManager(loader=loader, sources='localhost,')

# variable manager takes care of merging all the different sources to give you a unified view of variables available in each context
variable_manager = VariableManager(loader=loader, inventory=inventory)

# create data structure that represents our play, including tasks, this is basically what our YAML loader does internally.
play_source =  dict(
        name = "Ansible Play",
        hosts = 'localhost',
        gather_facts = 'no',
        tasks = [
            dict(action=dict(module='shell', args='ls'), register='shell_out'),
            dict(action=dict(module='debug', args=dict(msg='{{shell_out.stdout}}')))
         ]
    )

# Create play object, playbook objects use .load instead of init or new methods,
# this will also automatically create the task objects from the info provided in play_source
play = Play().load(play_source, variable_manager=variable_manager, loader=loader)

# Run it - instantiate task queue manager, which takes care of forking and setting up all objects to iterate over host list and tasks
tqm = None
try:
    tqm = TaskQueueManager(
              inventory=inventory,
              variable_manager=variable_manager,
              loader=loader,
              passwords=passwords,
              stdout_callback=results_callback,  # Use our custom callback instead of the ``default`` callback plugin, which prints to stdout
          )
    result = tqm.run(play) # most interesting data for a play is actually sent to the callback's methods
finally:
    # we always need to cleanup child procs and the structures we use to communicate with them
    if tqm is not None:
        tqm.cleanup()

    # Remove ansible tmpdir
    shutil.rmtree(C.DEFAULT_LOCAL_TMP, True)
  • 執行ad-hoc任務
#!/usr/bin/env python
# -*- coding: utf-8 -*-

import sys
from collections import namedtuple
# 核心類
# 用於讀取YAML和JSON格式的文件
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
# 存儲執行hosts的角色信息
from ansible.playbook.play import Play
# ansible底層用到的任務隊列
from ansible.executor.task_queue_manager import TaskQueueManager
# 核心類執行playbook
from ansible.executor.playbook_executor import PlaybookExecutor


def adhoc():
    """
    ad-hoc 調用
    資產配置信息  這個是通過 InventoryManager和VariableManager 定義
    執行選項 這個是通過namedtuple來定義
    執行對象和模塊 通過dict()來定義
    定義play 通過Play來定義
    最後通過 TaskQueueManager 的實例來執行play
    :return:
    """
    # 資產配置信息
    dl = DataLoader()
    im = InventoryManager(loader=dl, sources=["hosts"])
    vm = VariableManager(loader=dl, inventory=im)
    # 執行選項,這個類不是ansible的類,這個的功能就是爲了構造參數
    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"
    ])
    """
    這裏就是Options的實例,然後你就可以賦值,這個爲了給ansible設置執行選項 ansibile 172.16.48.171 -m shell -a 'ls /tmp' -f 5
    這裏的選項就是ansible命令中 -f -C -D -m等執行選項
    """
    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)
    # play的執行對象和模塊,這裏設置hosts,其實是因爲play把play_source和資產信息關聯後,執行的play的時候它會去資產信息中設置的sources的hosts文件中
    # 找你在play_source中設置的hosts是否在資產管理類裏面。
    play_source = dict(name="Ansible Play",  # 任務名稱
                       hosts="172.16.48.242",  # 目標主機,可以填寫具體主機也可以是主機組名稱
                       gather_facts="no",  # 是否收集配置信息

                       # tasks是具體執行的任務,列表形式,每個具體任務都是一個字典
                       tasks=[
                           dict(action=dict(module="shell", args="ls /tmp"))
                       ])
    # 定義play
    play = Play().load(play_source, variable_manager=vm, loader=dl)

    passwords = dict()  # 這個可以爲空,因爲在hosts文件中
    #
    tqm = TaskQueueManager(
        inventory=im,
        variable_manager=vm,
        loader=dl,
        options=options,
        passwords=passwords,
    )
    result = tqm.run(play)
    print(result)


def main():
    adhoc()


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

  • 執行playbook任務
#!/usr/bin/env python
# -*- coding: utf-8 -*-

import sys
from collections import namedtuple
# 核心類
# 用於讀取YAML和JSON格式的文件
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
# 存儲執行hosts的角色信息
from ansible.playbook.play import Play
# ansible底層用到的任務隊列
from ansible.executor.task_queue_manager import TaskQueueManager
# 核心類執行playbook
from ansible.executor.playbook_executor import PlaybookExecutor


def execplaybook():
    """
    調用 playbook
    調用playboo大致和調用ad-hoc相同,只是真正調用的是使用PlaybookExecutor
    :return:
    """
    # 資產配置信息
    dl = DataLoader()
    im = InventoryManager(loader=dl, sources=["hosts"])
    vm = VariableManager(loader=dl, inventory=im)
    # 執行選項,這個類不是ansible的類,這個的功能就是爲了構造參數
    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"
    ])
    """
    這裏就是Options的實例,然後你就可以賦值,這個爲了給ansible設置執行選項 ansibile 172.16.48.171 -m shell -a 'ls /tmp' -f 5
    這裏的選項就是ansible命令中 -f -C -D -m等執行選項
    """
    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)
    passwords = dict()  # 這個可以爲空,因爲在hosts文件中
    #
    try:
        # playbooks參數裏面放的就是playbook文件路徑
        playbook = PlaybookExecutor(playbooks=["f1.yml"], inventory=im, variable_manager=vm, loader=dl, options=options, passwords=passwords)
        playbook.run()
    except Exception as err:
        print(err)


def main():
    execplaybook()

if __name__ == "__main__":
    try:
        main()
    finally:
        sys.exit()
  • ansible管理iptables
#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""ansible管理iptables"""

import shutil

import ansible.constants as C
from ansible import context
from ansible.executor.task_queue_manager import TaskQueueManager
from ansible.inventory.manager import InventoryManager
from ansible.module_utils.common.collections import ImmutableDict
from ansible.parsing.dataloader import DataLoader
from ansible.playbook.play import Play
from ansible.plugins.callback import CallbackBase
from ansible.vars.manager import VariableManager


class ResultCallback(CallbackBase):
    """
    回調類,給ansible執行ad-hoc命令後回調使用
    """

    def __init__(self):
        super(ResultCallback, self).__init__()
        self.result = None

    def v2_runner_on_ok(self, result, **kwargs):
        """
        運行成功時回調
        :param result:
        :param kwargs:
        :return: 返回json對象
        """
        host = result._host
        self.result = {host.name: result._result}

    def v2_runner_on_failed(self, result, ignore_errors=False):
        """
        運行失敗時回調
        :param result:
        :param ignore_errors:
        :return: 返回json對象
        """
        host = result._host
        self.result = {host.name: result._result}

    def v2_runner_on_unreachable(self, result):
        """
        主機連接失敗時回調
        :param result:
        :return: 返回json對象
        """
        host = result._host
        self.result = {host.name: result._result}


class FireWall(object):
    """
    iptables規則核心類
    """

    def __init__(self, inventory_path, hosts='all', remote_user=None, become_user='root', password=None):
        """

        :param inventory_path: str  inventory文件路徑 必傳參數
        :param hosts: str 傳入的主機,和inventory文件的需要匹配上 默認所有主機
        :param remote_user: str 遠程連接用戶 非必傳參數 默認app用戶
        :param become_user: str 遠程執行命令用戶 非必傳參數 默認root用戶
        :param password: str 遠程主機密碼 非必傳參數 默認使用ssh公鑰對連接
        """
        context.CLIARGS = ImmutableDict(
            connection='smart',
            remote_user=remote_user,
            ack_pass=None,
            sudo_user=None,
            forks=5,
            sudo=True,
            ask_sudo_pass=False,
            verbosity=5,
            module_path=None,
            become=True,
            become_method='sudo',
            become_user=become_user,
            check=False,
            diff=False,
            listhosts=None,
            listtasks=None,
            listtags=None,
            syntax=None
        )
        self._loader = DataLoader()
        self._passwords = dict() if password is None else dict(vault_pass=password)
        self._results_callback = ResultCallback()
        self._inventory = InventoryManager(loader=self._loader, sources=[inventory_path])
        self._variable_manager = VariableManager(loader=self._loader, inventory=self._inventory)
        self._hosts = hosts
        self._redis_key = 'bdc_aomp_firewall_iptables_{}'.format('_'.join(self._hosts.split('.')))

    def _run_shell_task(self, name, cmd):
        """
        執行shell命令私有類
        :param name:  任務名稱
        :param cmd:   shell命令
        :return: 返回回調類的結果
        """
        play_source = dict(
            name=name,
            hosts=self._hosts,
            gather_facts='no',
            tasks=[
                dict(action=dict(module='shell', args=cmd), register='shell_out'),
            ]
        )

        play = Play().load(play_source, variable_manager=self._variable_manager, loader=self._loader)

        tqm = None
        try:
            tqm = TaskQueueManager(
                inventory=self._inventory,
                variable_manager=self._variable_manager,
                loader=self._loader,
                passwords=self._passwords,
                stdout_callback=self._results_callback,

            )
            tqm.run(play)
        finally:
            if tqm is not None:
                tqm.cleanup()

            shutil.rmtree(C.DEFAULT_LOCAL_TMP, True)
        return self._results_callback.result

    def _save_rule(self):
        """
        用來保存iptables的規則方法,默認添加和刪除的iptables規則保存在內存中,執行完新增和刪除規則後需要調用該方法
        :return: tuple (True, None)
        """
        cmd = 'service iptables save'
        res = self._run_shell_task('append rules', cmd)
        if res[self._hosts]['rc'] != 0:
            return False, res[self._hosts]['stderr']
        else:
            return True, None

    def get_iptables(self):
        """
        通過ansible獲取遠程主機的iptables規則
        :return: tuple (rules, None)
        """
        name = 'get iptables'
        cmd = 'iptables -nL --line-number'
        result = self._run_shell_task(name, cmd)
        if result is None:
            return None, 'get iptables failed!'
        if result[self._hosts].get('unreachable') is True:
            return None, result[self._hosts]['msg']
        if result[self._hosts]['rc'] != 0:
            return None, result[self._hosts]['stderr']
        try:
            forward_index = result[self._hosts]['stdout_lines'].index('Chain FORWARD (policy DROP)')
        except Exception:
            forward_index = result[self._hosts]['stdout_lines'].index('Chain FORWARD (policy ACCEPT)')
        output_index = result[self._hosts]['stdout_lines'].index('Chain OUTPUT (policy ACCEPT)')
        input_list = []
        forward_list = []
        output_list = []
        for i, data in enumerate(result[self._hosts]['stdout_lines']):
            res_dict = {
                'num': None,
                'target': None,
                'prot': None,
                'opt': None,
                'source': None,
                'destination': None,
                'rules': None
            }
            if len(data) > 0 and 'num' not in data and 'Chain' not in data:
                rules = data.split()
                res_dict['num'] = rules[0]
                res_dict['target'] = rules[1]
                res_dict['prot'] = rules[2]
                res_dict['opt'] = rules[3]
                res_dict['source'] = rules[4]
                res_dict['destination'] = rules[5]

                if len(rules) > 6:
                    res_dict['rules'] = ' '.join(rules[6:])
                if i < forward_index:
                    input_list.append(res_dict)
                elif i < output_index:
                    forward_list.append(res_dict)
                else:
                    output_list.append(res_dict)

        res = {"input": input_list, "forward": forward_list, "output": output_list}
        return res, None

    def get_hosts(self):
        """
        獲取管理的主機列表
        :return: dict
        """
        return {'hosts': [i.address for i in self._inventory.get_hosts()]}

    def append_rule(self, target, source=None, dpt=None, prot='all', chain='INPUT'):
        """
        向iptables規則後面新增規則
        :param target: str 放行還是屏蔽 指[ACCEPT, DROP]
        :param chain: str 出入站或者轉發的目標規則,大些,特指 [INPUT, FORWARD, OUTPUT]三個值
        :param source: str 源ip地址
        :param dpt: str 目的地端口
        :param prot: str 協議
        :return: tuple (True, None)
        """
        if not any([source, dpt]):
            return False, 'source 或者 dpt參數缺失'

        if dpt is not None and prot not in ['tcp', 'udp']:
            return False, 'dpt 參數不能出現在非tcp或者udp協議內'

        if source is None and prot in ['tcp', 'udp']:
            cmd = 'iptables -A {chain} -p {prot} --dport {dpt} -j {target}'.format(
                chain=chain, prot=prot, dpt=dpt, target=target
            )
        elif all([chain, prot, source, dpt]):
            cmd = 'iptables -A {chain} -p {prot} -s {source} --dport {dpt} -j {target}'.format(
                chain=chain, prot=prot, source=source, dpt=dpt, target=target
            )
        else:
            cmd = 'iptables -A {chain} -p {prot} -s {source} -j {target}'.format(
                chain=chain, prot=prot, source=source, target=target
            )
        res = self._run_shell_task('append rules', cmd)
        if res[self._hosts]['rc'] != 0:
            return False, res[self._hosts]['stderr']
        else:
            res, exception = self._save_rule()
            if exception is not None:
                return False, exception

            return True, None

    def delete_rule(self, num, chain='INPUT'):
        """
        刪除iptables規則
        :param chain: str 出入站或者轉發的目標規則,大些,特指 [INPUT, FORWARD, OUTPUT]三個值
        :param num: str 對應的股則序號id
        :return: tuple (True, None)
        """
        cmd = 'iptables -D {} {}'.format(chain, num)
        res = self._run_shell_task('append rules', cmd)
        if res[self._hosts]['rc'] != 0:
            return False, res[self._hosts]['stderr']
        else:
            res, exception = self._save_rule()
            if exception is not None:
                return False, exception

            return True, None

    def check_rules(self, num, chain='INPUT'):
        """
        檢查iptables的規則序號是否正確
        :param chain: str 出入站或者轉發的目標規則,大些,特指 [INPUT, FORWARD, OUTPUT]三個值
        :param num: str 出入站或者轉發的目標規則,大些,特指 [INPUT, FORWARD, OUTPUT]三個值
        :return: tuple (True, None)
        """
        res, exception = self.get_iptables()
        if exception is not None:
            return False, exception
        data = res[chain.lower()]
        nums = [i['num'] for i in data]
        if num not in nums:
            return False, '{}規則內沒有num爲{}的規則!'.format(chain, num)
        else:
            return True, None

    def search_rules(self, source=None, dpt=None, prot=None, chain='INPUT'):
        if not any([source, dpt, prot]):
            return None, '缺少查詢條件'
        data, err = self.get_iptables()
        if err is not None:
            return None, err

        data = data[chain.lower()]
        res = list()
        for i in data:
            if all([source, dpt, prot]):
                if source in i['source'] and (i['rules'] is not None and dpt in i['rules']) and prot in i['prot']:
                    res.append(i)
            elif all([source, dpt]):
                if source in i['source'] and (i['rules'] is not None and dpt in i['rules']):
                    res.append(i)
            elif all([dpt, prot]):
                if dpt in i['source'] and prot in i['prot']:
                    res.append(i)
            elif source:
                if source in i['source']:
                    res.append(i)
            elif dpt:
                if i['rules'] is not None and dpt in i['rules']:
                    res.append(i)
            elif prot:
                if prot in i['prot']:
                    res.append(i)
        return res, None

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章