您的位置:首页 > 编程语言 > Python开发

Python调用Ansible 2.0 API执行playbook

2017-09-14 17:12 706 查看
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 python

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.utils.vars import load_extra_vars
from ansible.utils.vars import load_options_vars
from ansible.executor.playbook_executor import PlaybookExecutor

Options = 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 variables
variable_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 gevent
from multiprocessing import  Queue

qq=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" % v

def 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 subprocess
import os

cmd = "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,可能理解不完全正确,如果哪位看官有解决方案,请不吝赐教,在此谢过。 
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  playbook python ansible