【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沒法鏈接,只給出了簡單的沒法鏈接的提示信息
基本使用就說到這裏吧。下面我要使用了,過程當中可能會要回過頭來改源碼。