【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及以后的版本,難度突然陡增,與此同時更多的更加底層的東西被開放給一般開發(fā),雖然復雜了,但是也更加靈活了。
對于一些簡單的需求,可能我們還是喜歡老版API,因此這里有個網(wǎng)上別人封裝好的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 CallbackBaseclass 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()] = resultdef v2_runner_on_ok(self, result, *args, **kwargs):self.host_ok[result._host.get_name()] = resultdef v2_runner_on_failed(self, result, *args, **kwargs):self.host_failed[result._host.get_name()] = resultclass MyInventory(Inventory):""" this is my ansible inventory object. """def __init__(self, resource, loader, variable_manager):""" resource的數(shù)據(jù)格式是一個列表字典,比如 { "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 = resourceself.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 groupif groupvars:for key, value in groupvars.iteritems():my_group.set_variable(key, value)# add hosts to groupfor host in hosts:# set connection variableshostname = 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 variablesfor 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 = resourceself.inventory = Noneself.variable_manager = Noneself.loader = Noneself.options = Noneself.passwords = Noneself.callback = Noneself.__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 objectsself.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).inventoryself.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 tasksplay_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 ittqm = Noneself.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.callbacktqm.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 = {} # 額外的參數(shù) sudoers.yml以及模板中的參數(shù),它對應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_strextra_vars['username'] = role_nameextra_vars['template_dir'] = template_fileextra_vars['command_list'] = temp_param.get('cmdList')extra_vars['role_uuid'] = 'role-%s' % role_uuidself.variable_manager.extra_vars = extra_vars# actually run itexecutor = 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.callbackexecutor.run()except Exception as e:print "error:",e.messagedef get_result(self):self.results_raw = {'success': {}, 'failed': {}, 'unreachable': {}}for host, result in self.callback.host_ok.items():self.results_raw['success'][host] = result._resultfor host, result in self.callback.host_failed.items():self.results_raw['failed'][host] = result._result.get('msg') or result._resultfor 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 AnsibleAPIresource = [{'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' # 模塊的參數(shù) )# 獲取結果,是一個字典格式,如果是print可以用json模塊美化一下 import json print json.dumps(api.get_result(),indent=4)?
從邏輯上看,首先我們聲明了一個resource,在這是一個list結構,其中包含了各個被操作主機的信息。hostname,ip,username,password這些是作為ansible連接所用的最基本的幾個參數(shù),此外port也可指定。resource被api類加載,這個過程其實就是生成了一個動態(tài)的inventory。從源碼中的90多行可以看出,當傳入一個list時api默認將其中所有host信息都放入一個default_group的inventory組,當傳入一個dict就默認這個dict的各個鍵值對分別是組名和組中host信息。
run方法是真正的執(zhí)行方法,其參數(shù)從前往后三個分別是host_list, module, module_args。command的話args比較簡單。像類似于copy這類模塊的參數(shù)可以這么寫:
api.run(['test'],'copy','src="/tmp/testfille" dest="/tmp/newfile"')
而file就可以這樣:
api.run(['test'],'path="/tmp/test.py" mode=0755 owner="tmpuser"')
通過這兩個例子基本就可以看清楚如何向run方法傳遞ansible模塊的參數(shù)了。
api在執(zhí)行run方法之后并不會主動輸出結果,需要我們手動地get_result()。result在空的情況下是這樣一個結構:
{"success": {},"failed": {},"unreachable":{} }?
不難看出,從主機層面上,主機們被分成了執(zhí)行成功,執(zhí)行失敗,和連接不到三類,分別給出結果。
下面給出一個返回結果的示例
{"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"} } View Code?
localtest執(zhí)行成功,返回中有changed,delta,start/end,stdout等可能要在后續(xù)處理中用到的各種數(shù)據(jù)
node-one執(zhí)行失敗,所以可以讀取stderr中的內容。由于返回是中文,在這里以unicode的形式展現(xiàn)
node-two無法連接,只給出了簡單的無法連接的提示信息
?
基本使用就說到這里吧。下面我要使用了,過程中可能會要回過頭來改源碼。
總結
以上是生活随笔為你收集整理的【Ansible】的python api的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 干货!一次kafka卡顿事故排查过程
- 下一篇: centos7 以上和以下版本设置