使用Python API 2.0.0.1运行Ansible Playbook

时间:2016-01-18 16:59:16

标签: python api ansible ansible-playbook

Ansible版本:2.0.0.1

我现在一直在四处寻找,我找到的大多数文档都不完整或已弃用(this post is for version 1.8.4, ie

我正在尝试通过Python API启动Ansible playbook。 Ansible的文档似乎展示了如何生成和播放任务,而不是如何加载和运行playbook yml文件。我一直在深入研究代码,试图了解如何启动它,我认为我已经取得了一些进展,但我真的打了一堵墙。这是我到目前为止所做的:

def createcluster(region, environment, cluster):
    Options = namedtuple('Options', ['region','env', 'cluster'])

    # initialize needed objects
    variable_manager = VariableManager()
    loader = DataLoader()
    options = Options(region=region, env=environment, cluster=cluster)
    options.listhosts = False
    vault_password = getpass.getpass('Enter vault password :')
    passwords = dict(vault_pass=vault_password)

    #Getting hosts
    hostsread = open('provisioning/inventory/hosts','r')
    hosts =  hostsread.read()
    inventory = Inventory(loader=loader, variable_manager=variable_manager, host_list=hosts)
    variable_manager.set_inventory(inventory)
    #Create and load the playbook file
    playbook = Playbook(loader)
    playbook.load('provisioning/cluster.yml', variable_manager,loader)

    #Create an executor to launch the playbook ? 
    executor = None
    executor = PlaybookExecutor(playbook,inventory,variable_manager,loader,options,passwords)
    try:
        result = executor.run()
    finally:
        if executor is not None:
            executor.cleanup()

我完全不确定执行程序部分,并且当我尝试启动代码时,我不断得到“AttributeError:'Options'对象没有属性'listhosts'”错误(奇怪的是它应该忽略它的缺席,我认为(line 60)

我应该如何加载YML文件并通过Python API启动它?我是走在好路上还是迷失了自己?为什么Ansible没有更好的记录?为什么42会成为7 * 7的答案?

4 个答案:

答案 0 :(得分:2)

以下是Ansible 2的示例:

#!/usr/bin/python2

from collections import namedtuple
from ansible.parsing.dataloader import DataLoader
from ansible.vars import VariableManager
from ansible.inventory import Inventory
from ansible.playbook import Playbook
from ansible.executor.playbook_executor import PlaybookExecutor

Options = namedtuple('Options', ['connection',  'forks', 'become', 'become_method', 'become_user', 'check', 'listhosts', 'listtasks', 'listtags', 'syntax', 'module_path'])

variable_manager = VariableManager()
loader = DataLoader()
options = Options(connection='local', forks=100, become=None, become_method=None, become_user=None, check=False, listhosts=False, listtasks=False, listtags=False, syntax=False, module_path="")
passwords = dict(vault_pass='secret')

inventory = Inventory(loader=loader, variable_manager=variable_manager, host_list='localhost')
variable_manager.set_inventory(inventory)
playbooks = ["./test.yaml"]

executor = PlaybookExecutor(
              playbooks=playbooks,
              inventory=inventory,
              variable_manager=variable_manager,
              loader=loader,
              options=options,
              passwords=passwords)

executor.run()

使用Python 2.7.10和ansible 2.0.1.0进行测试

答案 1 :(得分:2)

声明

发布完成。
我无法为ansible 2.4设置详细程度。我将主要谈论这个。

TL; DR

Ansible使用Display文件中的全局__main__对象(您启动的对象),如果它不存在则某些导入会创建它。
这被视为bad practicenot PEP8 compliant(第二个要点)

说明部分

版本:(我使用的是python virtualenv)

  • ansible = 2.4.2.0(也在2.4.1.0中测试)
  • python = 2.7.13

如何在ansible

中使用它
try:
    from __main__ import display
except ImportError:
    from ansible.utils.display import Display
    display = Display()

几乎每个文件(108)都会调用它。 就像这样你在入口点有一个新的显示,然后所有其他模块将检索这个第一个声明的显示。

以另一种详细程度运行

您只需声明一个Display对象:

from ansible.utils.display import Display
display = Display(verbosity=5)

您也可以在display.verbosity = 1000

之后使用此功能

问题

我希望能够完全删除ansible输出(负值=无输出)

解决

我最终创建了一个像这样的新类:

from ansible.utils.display import Display

class AnsibleDisplay(Display):
    ''' 
    This  class override the display.display() function
    '''
    def display(self, *args, **kwargs):
        if self.verbosity >= 0:
            super(AnsibleDisplay, self).display(*args, **kwargs)

然后将其导入我的__main__文件

# Ansible call this global object for display
sys.path.append(ROOT_DIR + os.sep + 'lib')
from ansible_display import AnsibleDisplay
display = AnsibleDisplay(verbosity=0)

仅在导入所有其他模块后

实施例

SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
ROOT_DIR = os.path.dirname(SCRIPT_DIR)

## For ansible
import json
from collections import namedtuple

# Ansible call this global object for display
sys.path.append(ROOT_DIR + os.sep + 'lib')
from ansible_display import AnsibleDisplay
display = AnsibleDisplay(verbosity=0)

# Load other libs after to make sure they all use the above 'display'
from ansible.parsing.dataloader import DataLoader
from ansible.vars.manager import VariableManager
from ansible.inventory.manager import InventoryManager
from ansible.playbook.play import Play
from ansible.executor.playbook_executor import PlaybookExecutor

def apply_verbosity(args):
    global display
    verb = -1 if args.verbosity is None else args.verbosity
    display.verbosity = verb

def ansible_part():
    playbook_path = "%s/ansible/main_playbook.yml" % (ROOT_DIR)
    inventory_path = "%s/watev/my_inventory.ini"

    Options = namedtuple('Options', ['connection', 'module_path', 'forks', 'become', 'become_method', 'become_user', 'check', 'diff', 'listhosts', 'listtasks', 'listtags', 'syntax'])
    # initialize needed objects
    loader = DataLoader()
    options = Options(connection='local', module_path='%s/' % (ROOT_DIR), forks=100, become=None, become_method=None, become_user=None, check=False,
                    diff=False, listhosts=True, listtasks=False, listtags=False, syntax=False)
    passwords = dict(vault_pass='secret')

    # create inventory and pass to var manager
    inventory = InventoryManager(loader=loader, sources=[inventory_path])
    variable_manager = VariableManager(loader=loader, inventory=inventory)

    pbex = PlaybookExecutor(playbooks=[playbook_path], inventory=inventory, variable_manager=variable_manager, loader=loader, options=options, passwords=passwords)
    results = pbex.run()

def main():
    ansible_part()

注释

  1. 我需要将4个选项添加到namedtuple:
    listhosts=True, listtasks=False, listtags=False, syntax=False
  2. import __main__使得调试变得不切实际,因为在使用调试器时(在我的情况下是pudb),__main__文件是调试器文件,因此from __main__ import display永远不会工作
  3. HTH

    [Edit1]:添加了备注

答案 2 :(得分:1)

多亏了这个帖子中的建议,我设法找到了一种简单的方法来改变冗长:

import ansible
class AnsibleDisplay(ansible.utils.display.Display):
    def display(self, *args, **kwargs):
        self.verbosity = int(os.getenv('ANSIBLE_VERBOSITY', 0))
        super(AnsibleDisplay, self).display(*args, **kwargs)

ansible.utils.display.Display = AnsibleDisplay

# Then import ansible packages after the patch

import ansible.constants as C
from ansible import context
from ansible.cli import CLI
from ansible.executor.playbook_executor import PlaybookExecutor

答案 3 :(得分:0)

我在没有看到你想要版本2的情况下写了这篇文章。离开它,虽然这不是正确答案。

这将在1.9中有效。您可以修改createcluster()命令来调用它。

def run_ansible():
  vaultpass = "password"
  inventory = ansible.inventory.Inventory("provisioning/inventory/hosts", vault_password=vaultpass)

  stats = callbacks.AggregateStats()
  playbook_cb = callbacks.PlaybookCallbacks(verbose=3)

  pb = ansible.playbook.PlayBook(
            playbook=playbook,
            inventory=inventory,
            extra_vars=parsed_extra_vars,
            #private_key_file="/path/to/key.pem",
            vault_password=vaultpass,
            stats=stats,
            callbacks=playbook_cb,
            runner_callbacks=callbacks.PlaybookRunnerCallbacks(stats, verbose=3)
        )
  pb.run()

  hosts = sorted(pb.stats.processed.keys())

  failed_hosts = []
  unreachable_hosts = []
  for h in hosts:
    t = pb.stats.summarize(h)
    if t['failures'] > 0:
      failed_hosts.append(h)
    if t['unreachable'] > 0:
      unreachable_hosts.append(h)

  print("failed hosts: ", failed_hosts)
  print("unreachable hosts: ", unreachable_hosts)

  retries = failed_hosts + unreachable_hosts
  print("retries:", retries)
  if len(retries) > 0:
    return 1
  return 0