zoukankan      html  css  js  c++  java
  • 【Ansible】的python api

    【Ansible API】

      Ansible本身就是由python写成,所有其对python形式的API的支持应该不错。

      其API分不同的版本,这个版本也就是ansible本身的版本,可以通过ansible --version命令查看或者在python中import ansible然后查看anisble.__version__。

      在2.0的版本以前,ansible的API十分简单。通过大概十几行代码就可以模拟通过ad-hoc的方式运行annsible命令的。

      但是在2.0及以后的版本,难度突然陡增,与此同时更多的更加底层的东西被开放给一般开发,虽然复杂了,但是也更加灵活了。

      对于一些简单的需求,可能我们还是喜欢老版API,因此这里有个网上别人封装好的2.0版本API简化文件,通过这里的代码我们可以依然简单地运行ansible的ad-hoc。同时,它的源码支持修改,从而达到更个性化的改造。

      *** 需要注意,下面这个代码只在2.0开头的几个版本中适用。至少到2.4.0之后,API又有了一些改动,下面的代码运行会出错。我懒得再研究2.4了,就干脆pip install ansible==2.0来配合这个库

      ansible_api.py:

    # -*- coding:utf-8 -*-
    
    import os
    import sys
    from collections import namedtuple
    from ansible.parsing.dataloader import DataLoader
    from ansible.vars import VariableManager
    from ansible.inventory import Inventory
    from ansible.inventory.group import Group
    from ansible.inventory.host import Host
    from ansible.playbook.play import Play
    from ansible.executor.task_queue_manager import TaskQueueManager
    from ansible.executor.playbook_executor import PlaybookExecutor
    from ansible.plugins.callback import CallbackBase
    
    class ResultsCollector(CallbackBase):
        def __init__(self, *args, **kwargs):
            super(ResultsCollector, self).__init__(*args, **kwargs)
            self.host_ok = {}
            self.host_unreachable = {}
            self.host_failed = {}
    
        def v2_runner_on_unreachable(self, result):
            self.host_unreachable[result._host.get_name()] = result
    
        def v2_runner_on_ok(self, result, *args, **kwargs):
            self.host_ok[result._host.get_name()] = result
    
        def v2_runner_on_failed(self, result, *args, **kwargs):
            self.host_failed[result._host.get_name()] = result
    
    class MyInventory(Inventory):
        """ 
        this is my ansible inventory object. 
        """
    
        def __init__(self, resource, loader, variable_manager):
            """ 
            resource的数据格式是一个列表字典,比如 
                { 
                    "group1": { 
                        "hosts": [{"hostname": "10.0.0.0", "port": "22", "username": "test", "password": "pass"}, ...], 
                        "vars": {"var1": value1, "var2": value2, ...} 
                    } 
                } 
    
            如果你只传入1个列表,这默认该列表内的所有主机属于my_group组,比如 
                [{"hostname": "10.0.0.0", "port": "22", "username": "test", "password": "pass"}, ...] 
            """
            self.resource = resource
            self.inventory = Inventory(loader=loader, variable_manager=variable_manager, host_list=[])
            self.gen_inventory()
    
        def my_add_group(self, hosts, groupname, groupvars=None):
            """ 
            add hosts to a group 
            """
            my_group = Group(name=groupname)
    
            # if group variables exists, add them to group
            if groupvars:
                for key, value in groupvars.iteritems():
                    my_group.set_variable(key, value)
    
                    # add hosts to group
            for host in hosts:
                # set connection variables
                hostname = host.get("hostname")
                hostip = host.get('ip', hostname)
                hostport = host.get("port")
                username = host.get("username")
                password = host.get("password")
                ssh_key = host.get("ssh_key")
                my_host = Host(name=hostname, port=hostport)
                my_host.set_variable('ansible_ssh_host', hostip)
                my_host.set_variable('ansible_ssh_port', hostport)
                my_host.set_variable('ansible_ssh_user', username)
                my_host.set_variable('ansible_ssh_pass', password)
                my_host.set_variable('ansible_ssh_private_key_file', ssh_key)
    
                # set other variables
                for key, value in host.iteritems():
                    if key not in ["hostname", "port", "username", "password"]:
                        my_host.set_variable(key, value)
                        # add to group
                my_group.add_host(my_host)
    
            self.inventory.add_group(my_group)
    
        def gen_inventory(self):
            """ 
            add hosts to inventory. 
            """
            if isinstance(self.resource, list):
                self.my_add_group(self.resource, 'default_group')
            elif isinstance(self.resource, dict):
                for groupname, hosts_and_vars in self.resource.iteritems():
                    self.my_add_group(hosts_and_vars.get("hosts"), groupname, hosts_and_vars.get("vars"))
    
    
    class AnsibleAPI(object):
        """ 
        This is a General object for parallel execute modules. 
        """
    
        def __init__(self, resource, *args, **kwargs):
            self.resource = resource
            self.inventory = None
            self.variable_manager = None
            self.loader = None
            self.options = None
            self.passwords = None
            self.callback = None
            self.__initializeData()
            self.results_raw = {}
    
        def __initializeData(self):
            """ 
            初始化ansible 
            """
            Options = namedtuple('Options', ['connection', 'module_path', 'forks', 'timeout', 'remote_user',
                                             'ask_pass', 'private_key_file', 'ssh_common_args', 'ssh_extra_args',
                                             'sftp_extra_args',
                                             'scp_extra_args', 'become', 'become_method', 'become_user', 'ask_value_pass',
                                             'verbosity',
                                             'check', 'listhosts', 'listtasks', 'listtags', 'syntax'])
    
            # initialize needed objects
            self.variable_manager = VariableManager()
            self.loader = DataLoader()
            self.options = Options(connection='smart', module_path='/usr/share/ansible', forks=100, timeout=10,
                                   remote_user='root', ask_pass=False, private_key_file=None, ssh_common_args=None,
                                   ssh_extra_args=None,
                                   sftp_extra_args=None, scp_extra_args=None, become=None, become_method=None,
                                   become_user='root', ask_value_pass=False, verbosity=None, check=False, listhosts=False,
                                   listtasks=False, listtags=False, syntax=False)
    
            self.passwords = dict(sshpass=None, becomepass=None)
            self.inventory = MyInventory(self.resource, self.loader, self.variable_manager).inventory
            self.variable_manager.set_inventory(self.inventory)
    
        def run(self, host_list, module_name, module_args):
            """ 
            run module from andible ad-hoc. 
            module_name: ansible module_name 
            module_args: ansible module args 
            """
            # create play with tasks
            play_source = dict(
                name="Ansible Play",
                hosts=host_list,
                gather_facts='no',
                tasks=[dict(action=dict(module=module_name, args=module_args))]
            )
            play = Play().load(play_source, variable_manager=self.variable_manager, loader=self.loader)
    
            # actually run it
            tqm = None
            self.callback = ResultsCollector()
            try:
                tqm = TaskQueueManager(
                    inventory=self.inventory,
                    variable_manager=self.variable_manager,
                    loader=self.loader,
                    options=self.options,
                    passwords=self.passwords,
                )
                tqm._stdout_callback = self.callback
                tqm.run(play)
            finally:
                if tqm is not None:
                    tqm.cleanup()
    
        def run_playbook(self, host_list, role_name, role_uuid, temp_param):
            """ 
            run ansible palybook 
            """
            try:
                self.callback = ResultsCollector()
                filenames = ['' + '/handlers/ansible/v1_0/sudoers.yml']  # playbook的路径
                template_file = ''  # 模板文件的路径
                if not os.path.exists(template_file):
                    sys.exit()
    
                extra_vars = {}  # 额外的参数 sudoers.yml以及模板中的参数,它对应ansible-playbook test.yml --extra-vars "host='aa' name='cc' "
                host_list_str = ','.join([item for item in host_list])
                extra_vars['host_list'] = host_list_str
                extra_vars['username'] = role_name
                extra_vars['template_dir'] = template_file
                extra_vars['command_list'] = temp_param.get('cmdList')
                extra_vars['role_uuid'] = 'role-%s' % role_uuid
                self.variable_manager.extra_vars = extra_vars
                # actually run it
                executor = PlaybookExecutor(
                    playbooks=filenames, inventory=self.inventory, variable_manager=self.variable_manager,
                    loader=self.loader,
                    options=self.options, passwords=self.passwords,
                )
                executor._tqm._stdout_callback = self.callback
                executor.run()
            except Exception as e:
                print "error:",e.message
    
        def get_result(self):
            self.results_raw = {'success': {}, 'failed': {}, 'unreachable': {}}
            for host, result in self.callback.host_ok.items():
                self.results_raw['success'][host] = result._result
    
            for host, result in self.callback.host_failed.items():
                self.results_raw['failed'][host] = result._result.get('msg') or result._result
    
            for host, result in self.callback.host_unreachable.items():
                self.results_raw['unreachable'][host] = result._result['msg']
    
            return self.results_raw

      简单的用法是这样的:

    # -*- coding:utf-8 -*-
    
    from ansible_api import AnsibleAPI
    
    resource = [
     {'hostname':'localtest','ip','192.168.178.59','username':'root','password':'xxx'},
      {'hostname':'localtest2','ip':'192.168.178.141','username':'root','password':'yyy'}  #有个小坑,hostname中不能有空格,否则这个host会被ansible无视
    ]
    
    api = AnsibleAPI(resource)
    
    # 开始模拟以ad-hoc方式运行ansible命令
    api.run(
      ['localtest','localtest2'],    # 指出本次运行涉及的主机,在resource中定义
      'command',    # 本次运行使用的模块
      'hostname'    # 模块的参数
    )
    
    # 获取结果,是一个字典格式,如果是print可以用json模块美化一下
    import json
    print json.dumps(api.get_result(),indent=4)

      从逻辑上看,首先我们声明了一个resource,在这是一个list结构,其中包含了各个被操作主机的信息。hostname,ip,username,password这些是作为ansible连接所用的最基本的几个参数,此外port也可指定。resource被api类加载,这个过程其实就是生成了一个动态的inventory。从源码中的90多行可以看出,当传入一个list时api默认将其中所有host信息都放入一个default_group的inventory组,当传入一个dict就默认这个dict的各个键值对分别是组名和组中host信息。

      run方法是真正的执行方法,其参数从前往后三个分别是host_list, module, module_args。command的话args比较简单。像类似于copy这类模块的参数可以这么写:

      api.run(['test'],'copy','src="/tmp/testfille" dest="/tmp/newfile"')

      而file就可以这样:

      api.run(['test'],'path="/tmp/test.py" mode=0755 owner="tmpuser"')

      通过这两个例子基本就可以看清楚如何向run方法传递ansible模块的参数了。

      api在执行run方法之后并不会主动输出结果,需要我们手动地get_result()。result在空的情况下是这样一个结构:

    {
        "success": {},
        "failed": {},
        "unreachable":{}
    }

      不难看出,从主机层面上,主机们被分成了执行成功,执行失败,和连接不到三类,分别给出结果。

      下面给出一个返回结果的示例

    {
        "failed": {
            "node-one": {
                "cmd": [
                    "cat", 
                    "/tmp/test"
                ], 
                "end": "2018-05-08 16:27:29.327685", 
                "_ansible_no_log": false, 
                "stdout": "", 
                "changed": true, 
                "failed": true, 
                "delta": "0:00:00.003369", 
                "stderr": "cat: /tmp/test: u6ca1u6709u90a3u4e2au6587u4ef6u6216u76eeu5f55", 
                "rc": 1, 
                "invocation": {
                    "module_name": "command", 
                    "module_args": {
                        "creates": null, 
                        "executable": null, 
                        "chdir": null, 
                        "_raw_params": "cat /tmp/test", 
                        "removes": null, 
                        "warn": true, 
                        "_uses_shell": false
                    }
                }, 
                "stdout_lines": [], 
                "start": "2018-05-08 16:27:29.324316", 
                "warnings": []
            }
        }, 
        "success": {
            "localtest": {
                "cmd": [
                    "cat", 
                    "/tmp/test"
                ], 
                "end": "2018-05-08 16:27:30.692616", 
                "_ansible_no_log": false, 
                "stdout": "", 
                "changed": true, 
                "start": "2018-05-08 16:27:30.689329", 
                "delta": "0:00:00.003287", 
                "stderr": "", 
                "rc": 0, 
                "invocation": {
                    "module_name": "command", 
                    "module_args": {
                        "creates": null, 
                        "executable": null, 
                        "chdir": null, 
                        "_raw_params": "cat /tmp/test", 
                        "removes": null, 
                        "warn": true, 
                        "_uses_shell": false
                    }
                }, 
                "stdout_lines": [], 
                "warnings": []
            }
        }, 
        "unreachable": {
            "node-two": "ERROR! SSH encountered an unknown error during the connection. We recommend you re-run the command using -vvvv, which will enable SSH debugging output to help diagnose the issue"
        }
    }
    View Code

      localtest执行成功,返回中有changed,delta,start/end,stdout等可能要在后续处理中用到的各种数据

      node-one执行失败,所以可以读取stderr中的内容。由于返回是中文,在这里以unicode的形式展现

      node-two无法连接,只给出了简单的无法连接的提示信息

      基本使用就说到这里吧。下面我要使用了,过程中可能会要回过头来改源码。

  • 相关阅读:
    HDU1542 Atlantis(矩形面积并)
    HDU4784 Dinner Coming Soon(dp)
    1023: [SHOI2008]cactus仙人掌图(DP+单调队列优化)
    BZOJ 1004: [HNOI2008]Cards(群论)
    BZOJ USACO 银组 水题集锦
    BZOJ 3401: [Usaco2009 Mar]Look Up 仰望(离线+平衡树)
    BZOJ 3404: [Usaco2009 Open]Cow Digit Game又见数字游戏(博弈论)
    BZOJ 3208: 花神的秒题计划Ⅰ
    BZOJ 2456: mode(乱搞)
    BZOJ 2424: [HAOI2010]订货(费用流)
  • 原文地址:https://www.cnblogs.com/franknihao/p/9009110.html
Copyright © 2011-2022 走看看