【Ansible API】node
Ansible自己就是由python写成,全部其对python形式的API的支持应该不错。python
其API分不一样的版本,这个版本也就是ansible自己的版本,能够经过ansible --version命令查看或者在python中import ansible而后查看anisble.__version__。shell
在2.0的版本之前,ansible的API十分简单。经过大概十几行代码就能够模拟经过ad-hoc的方式运行annsible命令的。json
可是在2.0及之后的版本,难度忽然陡增,与此同时更多的更加底层的东西被开放给通常开发,虽然复杂了,可是也更加灵活了。api
对于一些简单的需求,可能咱们仍是喜欢老版API,所以这里有个网上别人封装好的2.0版本API简化文件,经过这里的代码咱们能够依然简单地运行ansible的ad-hoc。同时,它的源码支持修改,从而达到更个性化的改造。ssh
*** 须要注意,下面这个代码只在2.0开头的几个版本中适用。至少到2.4.0以后,API又有了一些改动,下面的代码运行会出错。我懒得再研究2.4了,就干脆pip install ansible==2.0来配合这个库ide
ansible_api.py:ui
# -*- 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
简单的用法是这样的:this
# -*- 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信息。spa
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: \u6ca1\u6709\u90a3\u4e2a\u6587\u4ef6\u6216\u76ee\u5f55", "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" } }
localtest执行成功,返回中有changed,delta,start/end,stdout等可能要在后续处理中用到的各类数据
node-one执行失败,因此能够读取stderr中的内容。因为返回是中文,在这里以unicode的形式展示
node-two没法链接,只给出了简单的没法链接的提示信息
基本使用就说到这里吧。下面我要使用了,过程当中可能会要回过头来改源码。