Python调用Ansible 2.0 API执行playbook

来源:互联网 发布:ubuntu启动网络服务 编辑:程序博客网 时间:2024/06/06 04:43
Ansible社区目前非常活跃,从1.x到2.x,以及2.x以后的版本都有一些变化,Ansible官方并不支持Python API,不保证API向后兼容。2.0版本重写了大部分Python API,官网上说2.0后使用Ansible API有些复杂了。
由于最开始没有重视版本间的差异,本地git clone了最新的dev分支代码,按照dev分支的实现用Python调用Ansible API,结果放到运行环境上就报找不到import的module。最后发现运行环境上的是2.3.2.0版本的Ansible,API跟最新的dev分支有很大不同,所以只好把本地代码切换到2.3 stable版本。下面的代码实现了用Python调用Ansible在本机执行playbook的功能,但仅支持2.3版本,代码参考ansible/cli/playbook.py,实际上就是从命令行的执行逻辑中抽取出来的。
!/usr/bin/env pythonimport osimport sysfrom collections import namedtuplefrom ansible.parsing.dataloader import DataLoaderfrom ansible.vars import VariableManagerfrom ansible.inventory import Inventoryfrom ansible.utils.vars import load_extra_varsfrom ansible.utils.vars import load_options_varsfrom ansible.executor.playbook_executor import PlaybookExecutorOptions = namedtuple('Options', ['listtags', 'listtasks', 'listhosts', 'syntax', 'connection','module_path', 'forks', 'remote_user', 'private_key_file', 'ssh_common_args', 'ssh_extra_args', 'sftp_extra_args', 'scp_extra_args', 'become', 'become_method', 'become_user', 'verbosity', 'check', 'extra_vars'])options = Options(listtags=False, listtasks=False, listhosts=False, syntax=False, connection='local', module_path=None, forks=1, remote_user='', private_key_file=None, ssh_common_args='', ssh_extra_args='', sftp_extra_args='', scp_extra_args='', become=True, become_method='sudo', become_user='root', verbosity=3, check=False, extra_vars={})loader = DataLoader()# create the variable manager, which will be shared throughout# the code, ensuring a consistent view of global variablesvariable_manager = VariableManager()variable_manager.extra_vars = load_extra_vars(loader=loader, options=options)variable_manager.options_vars = load_options_vars(options)# create the inventory, and filter it based on the subset specified (if any)inventory = Inventory(loader=loader, variable_manager=variable_manager,  host_list=['localhost'])variable_manager.set_inventory(inventory)playbook_path = './p1.yaml'if not os.path.exists(playbook_path):    print '[INFO] The playbook does not exist'    sys.exit()passwords = {'conn_pass': '', 'become_pass': ''}pbex = PlaybookExecutor(playbooks=[playbook_path], inventory=inventory, variable_manager=variable_manager, loader=loader, options=options, passwords=passwords)results = pbex.run()print results
上面的方法使用独立的Python进程执行没有问题,返回的结果跟直接用命令行执行完全一样。但是如果需要运行在gevent环境中,代码可能会hang。原因是Ansible使用了multiprocessing的queue来保存执行结果,ansible一开始执行就初始化结果队列,尝试取结果,但是因为queue为空,且queue()函数默认是block的,所以相当于独占了整个线程,gevent无法切换。下面是问题的复现代码:
import geventfrom multiprocessing import  Queueqq=Queue()def foo():    print('Running in foo')    gevent.sleep(0)    print('Explicit context switch to foo again')def bar():    print('Explicit context to bar')    gevent.sleep(0)    print('Implicit context switch back to bar')def test_queue():    print "getting queue"    v = qq.get()    print "got %s" % vdef run_gevent():    gevent.joinall([        gevent.spawn(foo),        gevent.spawn(test_queue),        gevent.spawn(bar),    ])if __name__ == '__main__':    run_gevent()
一旦test_queue函数获得执行,整个进程就挂死了。

因为上面的方法不能在gevent环境中执行,所以退而求其次,尝试直接调用CLI命令。其实Ansible在执行时根据task不同经常是需要fork process的,一般使用Ansible的场景对执行速度要求不会很高,所以多创建几个进程也无伤大雅。
import subprocessimport oscmd = "ansible-playbook /opt/ansible/play.yaml"my_env = os.environ.copy()my_env["TOKEN"] = _get_token()process = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True, env=my_env)output, error = process.communicate()print output
上面给新创建的进程增加了一个环境变量TOKEN。

初步学习Ansible,特别是第一种方式调用Python API,可能理解不完全正确,如果哪位看官有解决方案,请不吝赐教,在此谢过。