Ansible API: 動態清單(Inventory)的使用

本文基於Ansible 2.7
Ansible官方文檔提供的示例如下:

#!/usr/bin/env python

import json
import shutil
from collections import namedtuple
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.task_queue_manager import TaskQueueManager
from ansible.plugins.callback import CallbackBase
import ansible.constants as C

class ResultCallback(CallbackBase):
    """A sample callback plugin used for performing an action as results come in

    If you want to collect all results into a single object for processing at
    the end of the execution, look into utilizing the ``json`` callback plugin
    or writing your own custom callback plugin
    """
    def v2_runner_on_ok(self, result, **kwargs):
        """Print a json representation of the result

        This method could store the result in an instance attribute for retrieval later
        """
        host = result._host
        print(json.dumps({host.name: result._result}, indent=4))

# since API is constructed for CLI it expects certain options to always be set, named tuple 'fakes' the args parsing options object
Options = namedtuple('Options', ['connection', 'module_path', 'forks', 'become', 'become_method', 'become_user', 'check', 'diff'])
options = Options(connection='local', module_path=['/to/mymodules'], forks=10, become=None, become_method=None, become_user=None, check=False, diff=False)

# initialize needed objects
loader = DataLoader() # Takes care of finding and reading yaml, json and ini files
passwords = dict(vault_pass='secret')

# Instantiate our ResultCallback for handling results as they come in. Ansible expects this to be one of its main display outlets
results_callback = ResultCallback()

# create inventory, use path to host config file as source or hosts in a comma separated string
inventory = InventoryManager(loader=loader, sources='localhost,')

# variable manager takes care of merging all the different sources to give you a unifed view of variables available in each context
variable_manager = VariableManager(loader=loader, inventory=inventory)

# create datastructure that represents our play, including tasks, this is basically what our YAML loader does internally.
play_source =  dict(
        name = "Ansible Play",
        hosts = 'localhost',
        gather_facts = 'no',
        tasks = [
            dict(action=dict(module='shell', args='ls'), register='shell_out'),
            dict(action=dict(module='debug', args=dict(msg='{{shell_out.stdout}}')))
         ]
    )

# Create play object, playbook objects use .load instead of init or new methods,
# this will also automatically create the task objects from the info provided in play_source
play = Play().load(play_source, variable_manager=variable_manager, loader=loader)

# Run it - instantiate task queue manager, which takes care of forking and setting up all objects to iterate over host list and tasks
tqm = None
try:
    tqm = TaskQueueManager(
              inventory=inventory,
              variable_manager=variable_manager,
              loader=loader,
              options=options,
              passwords=passwords,
              stdout_callback=results_callback,  # Use our custom callback instead of the ``default`` callback plugin, which prints to stdout
          )
    result = tqm.run(play) # most interesting data for a play is actually sent to the callback's methods
finally:
    # we always need to cleanup child procs and the structres we use to communicate with them
    if tqm is not None:
        tqm.cleanup()

    # Remove ansible tmpdir
    shutil.rmtree(C.DEFAULT_LOCAL_TMP, True)

可以看到

inventory = InventoryManager(loader=loader, sources='localhost,')

這裏使用的是靜態清單,localhost實際指向的是本地默認的清單文件(/etc/ansible/hosts),但如果我們想使用動態清單呢?
在簡單的需求中,是比較方便的。

#假設我們有一個IP地址的列表(這個列表可以通過合適的其他服務獲得,或者從數據庫直接查詢)
host_list = ['192.168.1.1','192.168.1.2']
#建立一個空的InventoryManager(sources默認就是None,此處就是明確一下)
inventory = InventoryManager(loader=loader, sources=None)
#下面開始向Inventory中添加Host
for host in host_list:
	inventory.add_host(host)
	#也可以分組,但此分組必須先顯式通過add_group方法添加
	#inventory.add_host(host,group='groupName')

這裏有一個地方需要注意,通過add_host添加的host,不能通過‘all’分組取到。也就是說,如果add_host的時候不指定分組,在play_source中指定 hosts = ‘all’ 是取不到host的,必須顯式的指定:

inventory.add_host(host,group='all')

如果我們需要使用host variable,就會麻煩一些。

InventoryManager.add_host這個方法,host參數必須是個字符串。而InventoryManager.get_host方法的返回值是一個Host對象:

  • ansible/inventory/manager.py
    def get_host(self, hostname):
        return self._inventory.get_host(hostname)
    def add_host(self, host, group=None, port=None):
        return self._inventory.add_host(host, group, port)

其中 self._inventory 是一個 InventoryData 對象

  • ansible/inventory/data.py
   def add_host(self, host, group=None, port=None):
       ''' adds a host to inventory and possibly a group if not there already '''

       if host:
           g = None
           if group:
               if group in self.groups:
                   g = self.groups[group]
               else:
                   raise AnsibleError("Could not find group %s in inventory" % group)

           if host not in self.hosts:
               h = Host(host, port)
               self.hosts[host] = h
               if self.current_source:  # set to 'first source' in which host was encountered
                   self.set_variable(host, 'inventory_file', self.current_source)
                   self.set_variable(host, 'inventory_dir', basedir(self.current_source))
               else:
                   self.set_variable(host, 'inventory_file', None)
                   self.set_variable(host, 'inventory_dir', None)
               display.debug("Added host %s to inventory" % (host))

               # set default localhost from inventory to avoid creating an implicit one. Last localhost defined 'wins'.
               if host in C.LOCALHOST:
                   if self.localhost is None:
                       self.localhost = self.hosts[host]
                       display.vvvv("Set default localhost to %s" % h)
                   else:
                       display.warning("A duplicate localhost-like entry was found (%s). First found localhost was %s" % (h, self.localhost.name))
           else:
               h = self.hosts[host]

           if g:
               g.add_host(h)
               self._groups_dict_cache = {}
               display.debug("Added host %s to group %s" % (host, group))
       else:
           raise AnsibleError("Invalid empty host name provided: %s" % host)
           
   def get_host(self, hostname):
       ''' fetch host object using name deal with implicit localhost '''

       matching_host = self.hosts.get(hostname, None)

       # if host is not in hosts dict
       if matching_host is None and hostname in C.LOCALHOST:
           # might need to create implicit localhost
           matching_host = self._create_implicit_localhost(hostname)

       return matching_host
  • ansible/inventory/host.py
    def __init__(self, name=None, port=None, gen_uuid=True):

        self.vars = {}
        self.groups = []
        self._uuid = None

        self.name = name
        self.address = name

        if port:
            self.set_variable('ansible_port', int(port))

        if gen_uuid:
            self._uuid = get_unique_id()
        self.implicit = False

可以看到Host的name和address值都是取形參name的值

所以我們只能通過下面這種辦法來使用host variable

host_list = ['192.168.1.1','192.168.1.2']
#建立一個空的InventoryManager(sources默認就是None,此處就是明確一下)
inventory = InventoryManager(loader=loader, sources=None)
#下面開始向Inventory中添加Host
for host in host_list:
	inventory.add_host(host)
	inventory.get_host(host).vars['var_name'] = 'var_val'

寫法十分僵硬。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章