【问题标题】:Running an Ansible Playbook using Python API 2.0.0.1使用 Python API 2.0.0.1 运行 Ansible Playbook
【发布时间】:2016-01-18 16:59:16
【问题描述】:

Ansible 版本:2.0.0.1

我现在已经四处寻找,发现的大多数文档要么不完整,要么已弃用 (this post is for version 1.8.4, ie)

我正在尝试通过 Python API 启动 Ansible 剧本。 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' object has no attribute 'listhosts'”错误(很奇怪,因为它应该忽略它的缺席,我认为(line 60))

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

【问题讨论】:

  • 用完了?您的意思是已弃用?
  • 啊,是的。更正:)

标签: python api ansible ansible-playbook


【解决方案1】:

这是 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 测试

【讨论】:

  • 你知道如何处理冗长吗?
  • 很遗憾没有,我尝试了很多东西,甚至看代码也无法调优。
  • 如果您有兴趣,我设法找到了一种方法。请阅读我在此线程中的帖子。
【解决方案2】:

免责声明

发布完成。
我在为 ansible 2.4 设置 详细程度 时遇到了麻烦。我主要讲这个。

TL;DR

Ansible 在 __main__ 文件(您启动的那个)中使用全局 Display 对象,如果它不存在,一些导入将创建它。
这被认为是 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 将永远无法工作

[Edit1]:添加注释

【讨论】:

    【解决方案3】:

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

    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
    

    【讨论】:

      【解决方案4】:

      我没有看到你想要第 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
      

      【讨论】:

      • 希望它能对其他人有所帮助...关于 2.0.0.1 版本的任何输入? ;)
      • 我还没有升级。我应该!
      • 为什么我在导入时收到此错误?我使用 Ansible v2.1.1.0 和 Python v2.7.11。 from ansible.parsing.dataloader import DataLoader\r\nImportError: No module named parsing.dataloader
      猜你喜欢
      • 2015-02-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-10-14
      • 2018-02-10
      相关资源
      最近更新 更多