mirror of
https://github.com/ansible-collections/community.general.git
synced 2025-07-24 05:40:23 -07:00
Transition inventory into plugins (#23001)
* draft new inventory plugin arch, yaml sample - split classes, moved out of init - extra debug statements - allow mulitple invenotry files - dont add hosts more than once - simplified host vars - since now we can have multiple, inventory_dir/file needs to be per host - ported yaml/script/ini/virtualbox plugins, dir is 'built in manager' - centralized localhost handling - added plugin docs - leaner meaner inventory (split to data + manager) - moved noop vars plugin - added 'postprocessing' inventory plugins - fixed ini plugin, better info on plugin run group declarations can appear in any position relative to children entry that contains them - grouphost_vars loading as inventory plugin (postprocessing) - playbook_dir allways full path - use bytes for file operations - better handling of empty/null sources - added test target that skips networking modules - now var manager loads play group/host_vars independant from inventory - centralized play setup repeat code - updated changelog with inv features - asperioribus verbis spatium album - fixed dataloader to new sig - made yaml plugin more resistant to bad data - nicer error msgs - fixed undeclared group detection - fixed 'ungrouping' - docs updated s/INI/file/ as its not only format - made behaviour of var merge a toggle - made 'source over group' path follow existing rule for var precedence - updated add_host/group from strategy - made host_list a plugin and added it to defaults - added advanced_host_list as example variation - refactored 'display' to be availbe by default in class inheritance - optimized implicit handling as per @pilou's feedback - removed unused code and tests - added inventory cache and vbox plugin now uses it - added _compose method for variable expressions in plugins - vbox plugin now uses 'compose' - require yaml extension for yaml - fix for plugin loader to always add original_path, even when not using all() - fix py3 issues - added --inventory as clearer option - return name when stringifying host objects - ajdust checks to code moving * reworked vars and vars precedence - vars plugins now load group/host_vars dirs - precedence for host vars is now configurable - vars_plugins been reworked - removed unused vars cache - removed _gathered_facts as we are not keeping info in host anymore - cleaned up tests - fixed ansible-pull to work with new inventory - removed version added notation to please rst check - inventory in config relative to config - ensures full paths on passed inventories * implicit localhost connection local
This commit is contained in:
parent
91a72ce7da
commit
8f97aef1a3
78 changed files with 3044 additions and 3003 deletions
|
@ -378,6 +378,8 @@ class PluginLoader:
|
|||
return None
|
||||
raise
|
||||
|
||||
# set extra info on the module, in case we want it later
|
||||
setattr(obj, '_original_path', path)
|
||||
return obj
|
||||
|
||||
def _display_plugin_load(self, class_name, name, searched_paths, path, found_in_cache=None, class_only=None):
|
||||
|
@ -433,8 +435,7 @@ class PluginLoader:
|
|||
if not issubclass(obj, plugin_class):
|
||||
continue
|
||||
|
||||
self._display_plugin_load(self.class_name, name, self._searched_paths, path,
|
||||
found_in_cache=found_in_cache, class_only=class_only)
|
||||
self._display_plugin_load(self.class_name, name, self._searched_paths, path, found_in_cache=found_in_cache, class_only=class_only)
|
||||
if not class_only:
|
||||
try:
|
||||
obj = obj(*args, **kwargs)
|
||||
|
@ -505,13 +506,6 @@ lookup_loader = PluginLoader(
|
|||
required_base_class='LookupBase',
|
||||
)
|
||||
|
||||
vars_loader = PluginLoader(
|
||||
'VarsModule',
|
||||
'ansible.plugins.vars',
|
||||
C.DEFAULT_VARS_PLUGIN_PATH,
|
||||
'vars_plugins',
|
||||
)
|
||||
|
||||
filter_loader = PluginLoader(
|
||||
'FilterModule',
|
||||
'ansible.plugins.filter',
|
||||
|
@ -548,3 +542,10 @@ terminal_loader = PluginLoader(
|
|||
'terminal_plugins'
|
||||
)
|
||||
|
||||
vars_loader = PluginLoader(
|
||||
'VarsModule',
|
||||
'ansible.plugins.vars',
|
||||
C.DEFAULT_VARS_PLUGIN_PATH,
|
||||
'vars_plugins',
|
||||
)
|
||||
|
||||
|
|
|
@ -28,7 +28,7 @@ from copy import deepcopy
|
|||
from ansible import constants as C
|
||||
from ansible.module_utils._text import to_text
|
||||
from ansible.utils.color import stringc
|
||||
from ansible.vars import strip_internal_keys
|
||||
from ansible.vars.manager import strip_internal_keys
|
||||
|
||||
try:
|
||||
from __main__ import display as global_display
|
||||
|
|
180
lib/ansible/plugins/inventory/__init__.py
Normal file
180
lib/ansible/plugins/inventory/__init__.py
Normal file
|
@ -0,0 +1,180 @@
|
|||
# (c) 2017, Red Hat, inc
|
||||
#
|
||||
# This file is part of Ansible
|
||||
#
|
||||
# Ansible is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Ansible is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
# Make coding more python3-ish
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
import os
|
||||
import hashlib
|
||||
import string
|
||||
|
||||
from ansible.errors import AnsibleError
|
||||
from ansible.module_utils._text import to_bytes
|
||||
from ansible.template import Templar
|
||||
|
||||
try:
|
||||
from __main__ import display
|
||||
except ImportError:
|
||||
from ansible.utils.display import Display
|
||||
display = Display()
|
||||
|
||||
|
||||
class BaseInventoryPlugin(object):
|
||||
""" Parses an Inventory Source"""
|
||||
|
||||
TYPE = 'generator'
|
||||
|
||||
def __init__(self, cache=None):
|
||||
|
||||
self.inventory = None
|
||||
self.display = display
|
||||
self.cache = cache
|
||||
|
||||
def parse(self, inventory, loader, path, cache=True):
|
||||
''' Populates self.groups from the given data. Raises an error on any parse failure. '''
|
||||
|
||||
self.loader = loader
|
||||
self.inventory = inventory
|
||||
|
||||
def verify_file(self, path):
|
||||
''' Verify if file is usable by this plugin, base does minimal accessability check '''
|
||||
|
||||
b_path = to_bytes(path)
|
||||
return (os.path.exists(b_path) and os.access(b_path, os.R_OK))
|
||||
|
||||
def get_cache_prefix(self, path):
|
||||
''' create predictable unique prefix for plugin/inventory '''
|
||||
|
||||
m = hashlib.sha1()
|
||||
m.update(self.NAME)
|
||||
d1 = m.hexdigest()
|
||||
|
||||
n = hashlib.sha1()
|
||||
n.update(path)
|
||||
d2 = n.hexdigest()
|
||||
|
||||
return 's_'.join([d1[:5], d2[:5]])
|
||||
|
||||
def clear_cache(self):
|
||||
pass
|
||||
|
||||
def populate_host_vars(self, hosts, variables, group, port=None):
|
||||
|
||||
if hosts:
|
||||
for host in hosts:
|
||||
self.inventory.add_host(host, group=group, port=port)
|
||||
if variables:
|
||||
for k in variables:
|
||||
self.inventory.set_variable(host, k, variables[k])
|
||||
|
||||
def _compose(self, template, variables):
|
||||
''' helper method for pluigns to compose variables for Ansible based on jinja2 expression and inventory vars'''
|
||||
t = Templar(loader=self.loader, variables=variables, disable_lookups=True)
|
||||
return t.do_template(template)
|
||||
|
||||
|
||||
|
||||
class BaseFileInventoryPlugin(BaseInventoryPlugin):
|
||||
""" Parses a File based Inventory Source"""
|
||||
|
||||
TYPE = 'storage'
|
||||
|
||||
def __init__(self, cache=None):
|
||||
|
||||
# file based inventories are always local so no need for cache
|
||||
super(BaseFileInventoryPlugin, self).__init__(cache=None)
|
||||
|
||||
|
||||
#### Helper methods ####
|
||||
def detect_range(line = None):
|
||||
'''
|
||||
A helper function that checks a given host line to see if it contains
|
||||
a range pattern described in the docstring above.
|
||||
|
||||
Returns True if the given line contains a pattern, else False.
|
||||
'''
|
||||
return '[' in line
|
||||
|
||||
def expand_hostname_range(line = None):
|
||||
'''
|
||||
A helper function that expands a given line that contains a pattern
|
||||
specified in top docstring, and returns a list that consists of the
|
||||
expanded version.
|
||||
|
||||
The '[' and ']' characters are used to maintain the pseudo-code
|
||||
appearance. They are replaced in this function with '|' to ease
|
||||
string splitting.
|
||||
|
||||
References: http://ansible.github.com/patterns.html#hosts-and-groups
|
||||
'''
|
||||
all_hosts = []
|
||||
if line:
|
||||
# A hostname such as db[1:6]-node is considered to consists
|
||||
# three parts:
|
||||
# head: 'db'
|
||||
# nrange: [1:6]; range() is a built-in. Can't use the name
|
||||
# tail: '-node'
|
||||
|
||||
# Add support for multiple ranges in a host so:
|
||||
# db[01:10:3]node-[01:10]
|
||||
# - to do this we split off at the first [...] set, getting the list
|
||||
# of hosts and then repeat until none left.
|
||||
# - also add an optional third parameter which contains the step. (Default: 1)
|
||||
# so range can be [01:10:2] -> 01 03 05 07 09
|
||||
|
||||
(head, nrange, tail) = line.replace('[','|',1).replace(']','|',1).split('|')
|
||||
bounds = nrange.split(":")
|
||||
if len(bounds) != 2 and len(bounds) != 3:
|
||||
raise AnsibleError("host range must be begin:end or begin:end:step")
|
||||
beg = bounds[0]
|
||||
end = bounds[1]
|
||||
if len(bounds) == 2:
|
||||
step = 1
|
||||
else:
|
||||
step = bounds[2]
|
||||
if not beg:
|
||||
beg = "0"
|
||||
if not end:
|
||||
raise AnsibleError("host range must specify end value")
|
||||
if beg[0] == '0' and len(beg) > 1:
|
||||
rlen = len(beg) # range length formatting hint
|
||||
if rlen != len(end):
|
||||
raise AnsibleError("host range must specify equal-length begin and end formats")
|
||||
fill = lambda _: str(_).zfill(rlen) # range sequence
|
||||
else:
|
||||
fill = str
|
||||
|
||||
try:
|
||||
i_beg = string.ascii_letters.index(beg)
|
||||
i_end = string.ascii_letters.index(end)
|
||||
if i_beg > i_end:
|
||||
raise AnsibleError("host range must have begin <= end")
|
||||
seq = list(string.ascii_letters[i_beg:i_end+1:int(step)])
|
||||
except ValueError: # not an alpha range
|
||||
seq = range(int(beg), int(end)+1, int(step))
|
||||
|
||||
for rseq in seq:
|
||||
hname = ''.join((head, fill(rseq), tail))
|
||||
|
||||
if detect_range(hname):
|
||||
all_hosts.extend( expand_hostname_range( hname ) )
|
||||
else:
|
||||
all_hosts.append(hname)
|
||||
|
||||
return all_hosts
|
||||
|
103
lib/ansible/plugins/inventory/advanced_host_list.py
Normal file
103
lib/ansible/plugins/inventory/advanced_host_list.py
Normal file
|
@ -0,0 +1,103 @@
|
|||
# Copyright 2017 RedHat, inc
|
||||
#
|
||||
# This file is part of Ansible
|
||||
#
|
||||
# Ansible is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Ansible is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
#############################################
|
||||
'''
|
||||
DOCUMENTATION:
|
||||
inventory: advanced_host_list
|
||||
version_added: "2.4"
|
||||
short_description: Parses a 'host list' with ranges
|
||||
description:
|
||||
- Parses a host list string as a comma separated values of hosts and supports host ranges.
|
||||
- This plugin only applies to inventory sources that are not paths and contain at least one comma.
|
||||
EXAMPLES:
|
||||
# simple range
|
||||
ansible -i 'host[1:10],' -m ping
|
||||
|
||||
# still supports w/o ranges also
|
||||
ansible-playbook -i 'localhost,' play.yml
|
||||
'''
|
||||
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
import os
|
||||
|
||||
from ansible.errors import AnsibleError, AnsibleParserError
|
||||
from ansible.module_utils._text import to_bytes, to_text, to_native
|
||||
from ansible.parsing.utils.addresses import parse_address
|
||||
from ansible.plugins.inventory import BaseInventoryPlugin, detect_range, expand_hostname_range
|
||||
|
||||
|
||||
class InventoryModule(BaseInventoryPlugin):
|
||||
|
||||
NAME = 'advanced_host_list'
|
||||
|
||||
def verify_file(self, host_list):
|
||||
|
||||
valid = False
|
||||
b_path = to_bytes(host_list)
|
||||
if not os.path.exists(b_path) and ',' in host_list:
|
||||
valid = True
|
||||
return valid
|
||||
|
||||
def parse(self, inventory, loader, host_list, cache=True):
|
||||
''' parses the inventory file '''
|
||||
|
||||
super(InventoryModule, self).parse(inventory, loader, host_list)
|
||||
|
||||
try:
|
||||
for h in host_list.split(','):
|
||||
if h:
|
||||
try:
|
||||
(hostnames, port) = self._expand_hostpattern(h)
|
||||
except AnsibleError as e:
|
||||
self.display.vvv("Unable to parse address from hostname, leaving unchanged: %s" % to_native(e))
|
||||
host = [h]
|
||||
port = None
|
||||
|
||||
for host in hostnames:
|
||||
if host not in self.inventory.hosts:
|
||||
self.inventory.add_host(host, group='ungrouped', port=port)
|
||||
else:
|
||||
self.display.warning("Skipping invalid hostname: %s" % to_text(h))
|
||||
except Exception as e:
|
||||
raise AnsibleParserError("Invalid data from string, could not parse: %s" % str(e))
|
||||
|
||||
def _expand_hostpattern(self, hostpattern):
|
||||
'''
|
||||
Takes a single host pattern and returns a list of hostnames and an
|
||||
optional port number that applies to all of them.
|
||||
'''
|
||||
# Can the given hostpattern be parsed as a host with an optional port
|
||||
# specification?
|
||||
|
||||
try:
|
||||
(pattern, port) = parse_address(hostpattern, allow_ranges=True)
|
||||
except:
|
||||
# not a recognizable host pattern
|
||||
pattern = hostpattern
|
||||
port = None
|
||||
|
||||
# Once we have separated the pattern, we expand it into list of one or
|
||||
# more hostnames, depending on whether it contains any [x:y] ranges.
|
||||
|
||||
if detect_range(pattern):
|
||||
hostnames = expand_hostname_range(pattern)
|
||||
else:
|
||||
hostnames = [pattern]
|
||||
|
||||
return (hostnames, port)
|
78
lib/ansible/plugins/inventory/host_list.py
Normal file
78
lib/ansible/plugins/inventory/host_list.py
Normal file
|
@ -0,0 +1,78 @@
|
|||
# Copyright 2017 RedHat, inc
|
||||
#
|
||||
# This file is part of Ansible
|
||||
#
|
||||
# Ansible is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Ansible is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
'''
|
||||
DOCUMENTATION:
|
||||
inventory: host_list
|
||||
version_added: "2.4"
|
||||
short_description: Parses a 'host list' string
|
||||
description:
|
||||
- Parses a host list string as a comma separated values of hosts
|
||||
- This plugin only applies to inventory strings that are not paths and contain a comma.
|
||||
EXAMPLES: |
|
||||
# define 2 hosts in command line
|
||||
ansible -i '10.10.2.6, 10.10.2.4' -m ping all
|
||||
|
||||
# DNS resolvable names
|
||||
ansible -i 'host1.example.com, host2' -m user -a 'name=me state=abset' all
|
||||
|
||||
# just use localhost
|
||||
ansible-playbook -i 'localhost,' play.yml -c local
|
||||
'''
|
||||
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
import os
|
||||
|
||||
from ansible.errors import AnsibleError, AnsibleParserError
|
||||
from ansible.module_utils.six import string_types
|
||||
from ansible.module_utils._text import to_bytes, to_text, to_native
|
||||
from ansible.parsing.utils.addresses import parse_address
|
||||
from ansible.plugins.inventory import BaseInventoryPlugin
|
||||
|
||||
|
||||
class InventoryModule(BaseInventoryPlugin):
|
||||
|
||||
NAME = 'host_list'
|
||||
|
||||
def verify_file(self, host_list):
|
||||
|
||||
valid = False
|
||||
b_path = to_bytes(host_list)
|
||||
if not os.path.exists(b_path) and ',' in host_list:
|
||||
valid = True
|
||||
return valid
|
||||
|
||||
def parse(self, inventory, loader, host_list, cache=True):
|
||||
''' parses the inventory file '''
|
||||
|
||||
super(InventoryModule, self).parse(inventory, loader, host_list)
|
||||
|
||||
try:
|
||||
for h in host_list.split(','):
|
||||
if h:
|
||||
try:
|
||||
(host, port) = parse_address(h, allow_ranges=False)
|
||||
except AnsibleError as e:
|
||||
self.display.vvv("Unable to parse address from hostname, leaving unchanged: %s" % to_native(e))
|
||||
host = h
|
||||
port = None
|
||||
|
||||
if host not in self.inventory.hosts:
|
||||
self.inventory.add_host(host, group='ungrouped', port=port)
|
||||
except Exception as e:
|
||||
raise AnsibleParserError("Invalid data from string, could not parse: %s" % str(e))
|
399
lib/ansible/plugins/inventory/ini.py
Normal file
399
lib/ansible/plugins/inventory/ini.py
Normal file
|
@ -0,0 +1,399 @@
|
|||
# Copyright 2015 Abhijit Menon-Sen <ams@2ndQuadrant.com>
|
||||
#
|
||||
# This file is part of Ansible
|
||||
#
|
||||
# Ansible is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Ansible is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
'''
|
||||
DOCUMENTATION:
|
||||
inventory: ini
|
||||
version_added: "2.4"
|
||||
short_description: Uses an Ansible INI file as inventory source.
|
||||
description:
|
||||
- INI file based inventory, sections are groups or group related with special `:modifiers`.
|
||||
- Entries in sections C([group_1]) are hosts, members of the group.
|
||||
- Hosts can have variables defined inline as key/value pairs separated by C(=).
|
||||
- The C(children) modifier indicates that the section contains groups.
|
||||
- The C(vars) modifier indicates that the section contains variables assigned to members of the group.
|
||||
- Anything found outside a section is considered an 'ungrouped' host.
|
||||
notes:
|
||||
- It takes the place of the previously hardcoded INI inventory.
|
||||
- To function it requires being whitelisted in configuration.
|
||||
|
||||
EXAMPLES:
|
||||
example1: |
|
||||
# example cfg file
|
||||
[web]
|
||||
host1
|
||||
host2 ansible_port=222
|
||||
|
||||
[web:vars]
|
||||
http_port=8080 # all members of 'web' will inherit these
|
||||
myvar=23
|
||||
|
||||
[web:children] # child groups will automatically add their hosts to partent group
|
||||
apache
|
||||
nginx
|
||||
|
||||
[apache]
|
||||
tomcat1
|
||||
tomcat2 myvar=34 # host specific vars override group vars
|
||||
|
||||
[nginx]
|
||||
jenkins1
|
||||
|
||||
[nginx:vars]
|
||||
has_java = True # vars in child groups override same in parent
|
||||
|
||||
[all:vars]
|
||||
has_java = False # 'all' is 'top' parent
|
||||
|
||||
example2: |
|
||||
# other example config
|
||||
host1 # this is 'ungrouped'
|
||||
|
||||
# both hsots have same IP but diff ports, also 'ungrouped'
|
||||
host2 ansible_host=127.0.0.1 ansible_port=44
|
||||
host3 ansible_host=127.0.0.1 ansible_port=45
|
||||
|
||||
[g1]
|
||||
host4
|
||||
|
||||
[g2]
|
||||
host4 # same host as above, but member of 2 groups, will inherit vars from both
|
||||
# inventory hostnames are unique
|
||||
'''
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
import ast
|
||||
import re
|
||||
|
||||
from ansible.plugins.inventory import BaseFileInventoryPlugin, detect_range, expand_hostname_range
|
||||
from ansible.parsing.utils.addresses import parse_address
|
||||
|
||||
from ansible.errors import AnsibleError, AnsibleParserError
|
||||
from ansible.module_utils._text import to_bytes, to_text
|
||||
from ansible.utils.shlex import shlex_split
|
||||
|
||||
|
||||
class InventoryModule(BaseFileInventoryPlugin):
|
||||
"""
|
||||
Takes an INI-format inventory file and builds a list of groups and subgroups
|
||||
with their associated hosts and variable settings.
|
||||
"""
|
||||
NAME = 'ini'
|
||||
_COMMENT_MARKERS = frozenset((u';', u'#'))
|
||||
b_COMMENT_MARKERS = frozenset((b';', b'#'))
|
||||
|
||||
def __init__(self):
|
||||
|
||||
super(InventoryModule, self).__init__()
|
||||
|
||||
self.patterns = {}
|
||||
self._filename = None
|
||||
|
||||
def parse(self, inventory, loader, path, cache=True):
|
||||
|
||||
super(InventoryModule, self).parse(inventory, loader, path)
|
||||
|
||||
self._filename = path
|
||||
|
||||
try:
|
||||
# Read in the hosts, groups, and variables defined in the
|
||||
# inventory file.
|
||||
if self.loader:
|
||||
(b_data, private) = self.loader._get_file_contents(path)
|
||||
else:
|
||||
b_path = to_bytes(path)
|
||||
with open(b_path, 'rb') as fh:
|
||||
b_data = fh.read()
|
||||
|
||||
try:
|
||||
# Faster to do to_text once on a long string than many
|
||||
# times on smaller strings
|
||||
data = to_text(b_data, errors='surrogate_or_strict').splitlines()
|
||||
except UnicodeError:
|
||||
# Handle non-utf8 in comment lines: https://github.com/ansible/ansible/issues/17593
|
||||
data = []
|
||||
for line in b_data.splitlines():
|
||||
if line and line[0] in self.b_COMMENT_MARKERS:
|
||||
# Replace is okay for comment lines
|
||||
#data.append(to_text(line, errors='surrogate_then_replace'))
|
||||
# Currently we only need these lines for accurate lineno in errors
|
||||
data.append(u'')
|
||||
else:
|
||||
# Non-comment lines still have to be valid uf-8
|
||||
data.append(to_text(line, errors='surrogate_or_strict'))
|
||||
|
||||
self._parse(path, data)
|
||||
except Exception as e:
|
||||
raise AnsibleParserError(e)
|
||||
|
||||
|
||||
def _raise_error(self, message):
|
||||
raise AnsibleError("%s:%d: " % (self._filename, self.lineno) + message)
|
||||
|
||||
def _parse(self, path, lines):
|
||||
'''
|
||||
Populates self.groups from the given array of lines. Raises an error on
|
||||
any parse failure.
|
||||
'''
|
||||
|
||||
self._compile_patterns()
|
||||
|
||||
# We behave as though the first line of the inventory is '[ungrouped]',
|
||||
# and begin to look for host definitions. We make a single pass through
|
||||
# each line of the inventory, building up self.groups and adding hosts,
|
||||
# subgroups, and setting variables as we go.
|
||||
|
||||
pending_declarations = {}
|
||||
groupname = 'ungrouped'
|
||||
state = 'hosts'
|
||||
|
||||
self.lineno = 0
|
||||
for line in lines:
|
||||
self.lineno += 1
|
||||
|
||||
line = line.strip()
|
||||
# Skip empty lines and comments
|
||||
if not line or line[0] in self._COMMENT_MARKERS:
|
||||
continue
|
||||
|
||||
# Is this a [section] header? That tells us what group we're parsing
|
||||
# definitions for, and what kind of definitions to expect.
|
||||
|
||||
m = self.patterns['section'].match(line)
|
||||
if m:
|
||||
(groupname, state) = m.groups()
|
||||
|
||||
state = state or 'hosts'
|
||||
if state not in ['hosts', 'children', 'vars']:
|
||||
title = ":".join(m.groups())
|
||||
self._raise_error("Section [%s] has unknown type: %s" % (title, state))
|
||||
|
||||
# If we haven't seen this group before, we add a new Group.
|
||||
#
|
||||
# Either [groupname] or [groupname:children] is sufficient to
|
||||
# declare a group, but [groupname:vars] is allowed only if the
|
||||
# group is declared elsewhere (not necessarily earlier). We add
|
||||
# the group anyway, but make a note in pending_declarations to
|
||||
# check at the end.
|
||||
|
||||
self.inventory.add_group(groupname)
|
||||
|
||||
if state == 'vars':
|
||||
pending_declarations[groupname] = dict(line=self.lineno, state=state, name=groupname)
|
||||
|
||||
# When we see a declaration that we've been waiting for, we can
|
||||
# delete the note.
|
||||
|
||||
if groupname in pending_declarations and state != 'vars':
|
||||
if pending_declarations[groupname]['state'] == 'children':
|
||||
self.inventory.add_child(pending_declarations[groupname]['parent'], groupname)
|
||||
del pending_declarations[groupname]
|
||||
|
||||
continue
|
||||
elif line.startswith('[') and line.endswith(']'):
|
||||
self._raise_error("Invalid section entry: '%s'. Please make sure that there are no spaces" % line +
|
||||
"in the section entry, and that there are no other invalid characters")
|
||||
|
||||
# It's not a section, so the current state tells us what kind of
|
||||
# definition it must be. The individual parsers will raise an
|
||||
# error if we feed them something they can't digest.
|
||||
|
||||
# [groupname] contains host definitions that must be added to
|
||||
# the current group.
|
||||
if state == 'hosts':
|
||||
hosts, port, variables = self._parse_host_definition(line)
|
||||
self.populate_host_vars(hosts, variables, groupname, port)
|
||||
|
||||
# [groupname:vars] contains variable definitions that must be
|
||||
# applied to the current group.
|
||||
elif state == 'vars':
|
||||
(k, v) = self._parse_variable_definition(line)
|
||||
self.inventory.set_variable(groupname, k, v)
|
||||
|
||||
# [groupname:children] contains subgroup names that must be
|
||||
# added as children of the current group. The subgroup names
|
||||
# must themselves be declared as groups, but as before, they
|
||||
# may only be declared later.
|
||||
elif state == 'children':
|
||||
child = self._parse_group_name(line)
|
||||
if child not in self.inventory.groups:
|
||||
pending_declarations[child] = dict(line=self.lineno, state=state, name=child, parent=groupname)
|
||||
else:
|
||||
self.inventory.add_child(groupname, child)
|
||||
|
||||
# This is a fencepost. It can happen only if the state checker
|
||||
# accepts a state that isn't handled above.
|
||||
else:
|
||||
self._raise_error("Entered unhandled state: %s" % (state))
|
||||
|
||||
# Any entries in pending_declarations not removed by a group declaration above mean that there was an unresolved reference.
|
||||
# We report only the first such error here.
|
||||
|
||||
for g in pending_declarations:
|
||||
if g not in self.inventory.groups:
|
||||
decl = pending_declarations[g]
|
||||
if decl['state'] == 'vars':
|
||||
raise AnsibleError("%s:%d: Section [%s:vars] not valid for undefined group: %s" % (path, decl['line'], decl['name'], decl['name']))
|
||||
elif decl['state'] == 'children':
|
||||
raise AnsibleError("%s:%d: Section [%s:children] includes undefined group: %s" % (path, decl['line'], decl['parent'], decl['name']))
|
||||
|
||||
def _parse_group_name(self, line):
|
||||
'''
|
||||
Takes a single line and tries to parse it as a group name. Returns the
|
||||
group name if successful, or raises an error.
|
||||
'''
|
||||
|
||||
m = self.patterns['groupname'].match(line)
|
||||
if m:
|
||||
return m.group(1)
|
||||
|
||||
self._raise_error("Expected group name, got: %s" % (line))
|
||||
|
||||
def _parse_variable_definition(self, line):
|
||||
'''
|
||||
Takes a string and tries to parse it as a variable definition. Returns
|
||||
the key and value if successful, or raises an error.
|
||||
'''
|
||||
|
||||
# TODO: We parse variable assignments as a key (anything to the left of
|
||||
# an '='"), an '=', and a value (anything left) and leave the value to
|
||||
# _parse_value to sort out. We should be more systematic here about
|
||||
# defining what is acceptable, how quotes work, and so on.
|
||||
|
||||
if '=' in line:
|
||||
(k, v) = [e.strip() for e in line.split("=", 1)]
|
||||
return (k, self._parse_value(v))
|
||||
|
||||
self._raise_error("Expected key=value, got: %s" % (line))
|
||||
|
||||
def _parse_host_definition(self, line ):
|
||||
'''
|
||||
Takes a single line and tries to parse it as a host definition. Returns
|
||||
a list of Hosts if successful, or raises an error.
|
||||
'''
|
||||
|
||||
# A host definition comprises (1) a non-whitespace hostname or range,
|
||||
# optionally followed by (2) a series of key="some value" assignments.
|
||||
# We ignore any trailing whitespace and/or comments. For example, here
|
||||
# are a series of host definitions in a group:
|
||||
#
|
||||
# [groupname]
|
||||
# alpha
|
||||
# beta:2345 user=admin # we'll tell shlex
|
||||
# gamma sudo=True user=root # to ignore comments
|
||||
|
||||
try:
|
||||
tokens = shlex_split(line, comments=True)
|
||||
except ValueError as e:
|
||||
self._raise_error("Error parsing host definition '%s': %s" % (line, e))
|
||||
|
||||
(hostnames, port) = self._expand_hostpattern(tokens[0])
|
||||
|
||||
# Try to process anything remaining as a series of key=value pairs.
|
||||
variables = {}
|
||||
for t in tokens[1:]:
|
||||
if '=' not in t:
|
||||
self._raise_error("Expected key=value host variable assignment, got: %s" % (t))
|
||||
(k, v) = t.split('=', 1)
|
||||
variables[k] = self._parse_value(v)
|
||||
|
||||
return hostnames, port, variables
|
||||
|
||||
def _expand_hostpattern(self, hostpattern):
|
||||
'''
|
||||
Takes a single host pattern and returns a list of hostnames and an
|
||||
optional port number that applies to all of them.
|
||||
'''
|
||||
|
||||
# Can the given hostpattern be parsed as a host with an optional port
|
||||
# specification?
|
||||
|
||||
try:
|
||||
(pattern, port) = parse_address(hostpattern, allow_ranges=True)
|
||||
except:
|
||||
# not a recognizable host pattern
|
||||
pattern = hostpattern
|
||||
port = None
|
||||
|
||||
# Once we have separated the pattern, we expand it into list of one or
|
||||
# more hostnames, depending on whether it contains any [x:y] ranges.
|
||||
|
||||
if detect_range(pattern):
|
||||
hostnames = expand_hostname_range(pattern)
|
||||
else:
|
||||
hostnames = [pattern]
|
||||
|
||||
return (hostnames, port)
|
||||
|
||||
@staticmethod
|
||||
def _parse_value(v):
|
||||
'''
|
||||
Attempt to transform the string value from an ini file into a basic python object
|
||||
(int, dict, list, unicode string, etc).
|
||||
'''
|
||||
try:
|
||||
v = ast.literal_eval(v)
|
||||
# Using explicit exceptions.
|
||||
# Likely a string that literal_eval does not like. We wil then just set it.
|
||||
except ValueError:
|
||||
# For some reason this was thought to be malformed.
|
||||
pass
|
||||
except SyntaxError:
|
||||
# Is this a hash with an equals at the end?
|
||||
pass
|
||||
return to_text(v, nonstring='passthru', errors='surrogate_or_strict')
|
||||
|
||||
def _compile_patterns(self):
|
||||
'''
|
||||
Compiles the regular expressions required to parse the inventory and
|
||||
stores them in self.patterns.
|
||||
'''
|
||||
|
||||
# Section names are square-bracketed expressions at the beginning of a
|
||||
# line, comprising (1) a group name optionally followed by (2) a tag
|
||||
# that specifies the contents of the section. We ignore any trailing
|
||||
# whitespace and/or comments. For example:
|
||||
#
|
||||
# [groupname]
|
||||
# [somegroup:vars]
|
||||
# [naughty:children] # only get coal in their stockings
|
||||
|
||||
self.patterns['section'] = re.compile(
|
||||
r'''^\[
|
||||
([^:\]\s]+) # group name (see groupname below)
|
||||
(?::(\w+))? # optional : and tag name
|
||||
\]
|
||||
\s* # ignore trailing whitespace
|
||||
(?:\#.*)? # and/or a comment till the
|
||||
$ # end of the line
|
||||
''', re.X
|
||||
)
|
||||
|
||||
# FIXME: What are the real restrictions on group names, or rather, what
|
||||
# should they be? At the moment, they must be non-empty sequences of non
|
||||
# whitespace characters excluding ':' and ']', but we should define more
|
||||
# precise rules in order to support better diagnostics.
|
||||
|
||||
self.patterns['groupname'] = re.compile(
|
||||
r'''^
|
||||
([^:\]\s]+)
|
||||
\s* # ignore trailing whitespace
|
||||
(?:\#.*)? # and/or a comment till the
|
||||
$ # end of the line
|
||||
''', re.X
|
||||
)
|
188
lib/ansible/plugins/inventory/script.py
Normal file
188
lib/ansible/plugins/inventory/script.py
Normal file
|
@ -0,0 +1,188 @@
|
|||
# (c) 2012-2014, Michael DeHaan <michael.dehaan@gmail.com>
|
||||
#
|
||||
# This file is part of Ansible
|
||||
#
|
||||
# Ansible is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Ansible is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
'''
|
||||
DOCUMENTATION:
|
||||
inventory: script
|
||||
version_added: "2.4"
|
||||
short_description: Executes an inventory script that returns JSON
|
||||
description:
|
||||
- The source provided must an executable that returns Ansible inventory JSON
|
||||
- The source must accept C(--list) and C(--host <hostname>) as arguments.
|
||||
C(--host) will only be used if no C(_meta) key is present (performance optimization)
|
||||
notes:
|
||||
- It takes the place of the previously hardcoded script inventory.
|
||||
- To function it requires being whitelisted in configuration, which is true by default.
|
||||
'''
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
from collections import Mapping
|
||||
|
||||
from ansible.errors import AnsibleError, AnsibleParserError
|
||||
from ansible.module_utils.basic import json_dict_bytes_to_unicode
|
||||
from ansible.module_utils.six import iteritems
|
||||
from ansible.module_utils._text import to_native, to_text
|
||||
from ansible.plugins.inventory import BaseInventoryPlugin
|
||||
|
||||
|
||||
class InventoryModule(BaseInventoryPlugin):
|
||||
''' Host inventory parser for ansible using external inventory scripts. '''
|
||||
|
||||
NAME = 'script'
|
||||
|
||||
def __init__(self):
|
||||
|
||||
super(InventoryModule, self).__init__()
|
||||
|
||||
self._hosts = set()
|
||||
|
||||
def verify_file(self, path):
|
||||
''' Verify if file is usable by this plugin, base does minimal accesability check '''
|
||||
|
||||
valid = super(InventoryModule, self).verify_file(path)
|
||||
|
||||
if valid:
|
||||
# not only accessible, file must be executable and/or have shebang
|
||||
shebang_present = False
|
||||
try:
|
||||
with open(path, 'rb') as inv_file:
|
||||
initial_chars = inv_file.read(2)
|
||||
if initial_chars.startswith(b'#!'):
|
||||
shebang_present = True
|
||||
except:
|
||||
pass
|
||||
|
||||
if not os.access(path, os.X_OK) and not shebang_present:
|
||||
valid = False
|
||||
|
||||
return valid
|
||||
|
||||
def parse(self, inventory, loader, path, cache=True):
|
||||
|
||||
super(InventoryModule, self).parse(inventory, loader, path)
|
||||
|
||||
# Support inventory scripts that are not prefixed with some
|
||||
# path information but happen to be in the current working
|
||||
# directory when '.' is not in PATH.
|
||||
path = os.path.abspath(path)
|
||||
cmd = [ path, "--list" ]
|
||||
try:
|
||||
try:
|
||||
sp = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
except OSError as e:
|
||||
raise AnsibleError("problem running %s (%s)" % (' '.join(cmd), e))
|
||||
(stdout, stderr) = sp.communicate()
|
||||
|
||||
path = to_native(path)
|
||||
if stderr:
|
||||
err = to_native(stderr) + "\n"
|
||||
|
||||
if sp.returncode != 0:
|
||||
raise AnsibleError("Inventory script (%s) had an execution error: %s " % (path, err))
|
||||
|
||||
# make sure script output is unicode so that json loader will output
|
||||
# unicode strings itself
|
||||
try:
|
||||
data = to_text(stdout, errors="strict")
|
||||
except Exception as e:
|
||||
raise AnsibleError("Inventory {0} contained characters that cannot be interpreted as UTF-8: {1}".format(path, to_native(e)))
|
||||
|
||||
try:
|
||||
processed = self.loader.load(data)
|
||||
except Exception as e:
|
||||
raise AnsibleError("failed to parse executable inventory script results from {0}: {1}\n{2}".format(path, to_native(e), err))
|
||||
|
||||
if not isinstance(processed, Mapping):
|
||||
raise AnsibleError("failed to parse executable inventory script results from {0}: needs to be a json dict\n{1}".format(path, err))
|
||||
|
||||
group = None
|
||||
data_from_meta = None
|
||||
for (group, gdata) in data.items():
|
||||
if group == '_meta':
|
||||
if 'hostvars' in data:
|
||||
data_from_meta = data['hostvars']
|
||||
else:
|
||||
self.parse_group(group, gdata)
|
||||
|
||||
# in Ansible 1.3 and later, a "_meta" subelement may contain
|
||||
# a variable "hostvars" which contains a hash for each host
|
||||
# if this "hostvars" exists at all then do not call --host for each
|
||||
# host. This is for efficiency and scripts should still return data
|
||||
# if called with --host for backwards compat with 1.2 and earlier.
|
||||
for host in self._hosts:
|
||||
got = {}
|
||||
if data_from_meta is None:
|
||||
got = self.get_host_variables(path, host, data_from_meta)
|
||||
else:
|
||||
try:
|
||||
got = data.get(host, {})
|
||||
except AttributeError as e:
|
||||
raise AnsibleError("Improperly formatted host information for %s: %s" % (host,to_native(e)))
|
||||
|
||||
self.populate_host_vars(host, got, group)
|
||||
|
||||
except Exception as e:
|
||||
raise AnsibleParserError(e)
|
||||
|
||||
|
||||
def _parse_group(self, group, data):
|
||||
|
||||
self.inventory.add_group(group)
|
||||
|
||||
if not isinstance(data, dict):
|
||||
data = {'hosts': data}
|
||||
# is not those subkeys, then simplified syntax, host with vars
|
||||
elif not any(k in data for k in ('hosts','vars','children')):
|
||||
data = {'hosts': [group], 'vars': data}
|
||||
|
||||
if 'hosts' in data:
|
||||
if not isinstance(data['hosts'], list):
|
||||
raise AnsibleError("You defined a group '%s' with bad data for the host list:\n %s" % (group, data))
|
||||
|
||||
for hostname in data['hosts']:
|
||||
self._hosts.add(hostname)
|
||||
self.inventory.add_host(hostname, group)
|
||||
|
||||
if 'vars' in data:
|
||||
if not isinstance(data['vars'], dict):
|
||||
raise AnsibleError("You defined a group '%s' with bad data for variables:\n %s" % (group, data))
|
||||
|
||||
for k, v in iteritems(data['vars']):
|
||||
self.inventory.set_variable(group, k, v)
|
||||
|
||||
if group != 'meta' and isinstance(data, dict) and 'children' in data:
|
||||
for child_name in data['children']:
|
||||
self.inventory.add_group(child_name)
|
||||
self.inventory.add_child(group, child_name)
|
||||
|
||||
def get_host_variables(self, path, host):
|
||||
""" Runs <script> --host <hostname>, to determine additional host variables """
|
||||
|
||||
cmd = [path, "--host", host]
|
||||
try:
|
||||
sp = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
except OSError as e:
|
||||
raise AnsibleError("problem running %s (%s)" % (' '.join(cmd), e))
|
||||
(out, err) = sp.communicate()
|
||||
if out.strip() == '':
|
||||
return {}
|
||||
try:
|
||||
return json_dict_bytes_to_unicode(self.loader.load(out))
|
||||
except ValueError:
|
||||
raise AnsibleError("could not parse post variable response: %s, %s" % (cmd, out))
|
169
lib/ansible/plugins/inventory/virtualbox.py
Executable file
169
lib/ansible/plugins/inventory/virtualbox.py
Executable file
|
@ -0,0 +1,169 @@
|
|||
# This file is part of Ansible,
|
||||
# (c) 2012-2017, Michael DeHaan <michael.dehaan@gmail.com>
|
||||
#
|
||||
# This file is part of Ansible
|
||||
#
|
||||
# Ansible is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Ansible is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
#############################################
|
||||
'''
|
||||
DOCUMENTATION:
|
||||
name: virtualbox
|
||||
plugin_type: inventory
|
||||
short_description: virtualbox inventory source
|
||||
description:
|
||||
- Get inventory hosts from the local virtualbox installation.
|
||||
- Uses a <name>.vbox.conf YAML configuration file.
|
||||
options:
|
||||
running_only:
|
||||
description: toggles showing all vms vs only those currently running
|
||||
default: False
|
||||
settings_password_file:
|
||||
description: provide a file containing the settings password (equivalent to --settingspwfile)
|
||||
network_info_path:
|
||||
description: property path to query for network information (ansible_host)
|
||||
default: "/VirtualBox/GuestInfo/Net/0/V4/IP"
|
||||
'''
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
from subprocess import Popen, PIPE
|
||||
|
||||
from ansible.errors import AnsibleParserError
|
||||
from ansible.module_utils._text import to_bytes
|
||||
from ansible.plugins.inventory import BaseInventoryPlugin
|
||||
|
||||
|
||||
class InventoryModule(BaseInventoryPlugin):
|
||||
''' Host inventory parser for ansible using external inventory scripts. '''
|
||||
|
||||
NAME = 'virtualbox'
|
||||
|
||||
def verify_file(self, path):
|
||||
|
||||
valid = False
|
||||
if super(InventoryModule, self).verify_file(path):
|
||||
if path.endswith('.vbox.conf'):
|
||||
valid = True
|
||||
return valid
|
||||
|
||||
def parse(self, inventory, loader, path, cache=True):
|
||||
|
||||
super(InventoryModule, self).parse(inventory, loader, path)
|
||||
|
||||
VBOX = "VBoxManage"
|
||||
cache_key = self.get_cache_prefix(path)
|
||||
|
||||
if cache and cache_key not in inventory.cache:
|
||||
source_data = inventory.cache[cache_key]
|
||||
else:
|
||||
# file is config file
|
||||
try:
|
||||
data = self.loader.load_from_file(path)
|
||||
except Exception as e:
|
||||
raise AnsibleParserError(e)
|
||||
|
||||
if not data or data.get('plugin') != self.NAME:
|
||||
return False
|
||||
|
||||
pwfile = to_bytes(data.get('settings_password_file'))
|
||||
netinfo = data.get('network_info_path', "/VirtualBox/GuestInfo/Net/0/V4/IP")
|
||||
running = data.get('running_only', False)
|
||||
|
||||
# start getting data
|
||||
cmd = [VBOX, 'list', '-l']
|
||||
if running:
|
||||
cmd.append('runningvms')
|
||||
else:
|
||||
cmd.append('vms')
|
||||
|
||||
if pwfile and os.path.exists(pwfile):
|
||||
cmd.append('--settingspwfile')
|
||||
cmd.append(pwfile)
|
||||
|
||||
try:
|
||||
p = Popen(cmd, stdout=PIPE)
|
||||
except Exception as e:
|
||||
AnsibleParserError(e)
|
||||
|
||||
hostvars = {}
|
||||
prevkey = pref_k = ''
|
||||
current_host = None
|
||||
|
||||
source_data = p.stdout.readlines()
|
||||
inventory.cache[cache_key] = source_data
|
||||
|
||||
for line in source_data:
|
||||
|
||||
try:
|
||||
k, v = line.split(':', 1)
|
||||
except:
|
||||
# skip non splitable
|
||||
continue
|
||||
|
||||
if k.strip() == '':
|
||||
# skip empty
|
||||
continue
|
||||
|
||||
v = v.strip()
|
||||
# found host
|
||||
if k.startswith('Name') and ',' not in v: # some setting strings appear in Name
|
||||
current_host = v
|
||||
if current_host not in hostvars:
|
||||
hostvars[current_host] = {}
|
||||
self.inventory.add_host(current_host)
|
||||
# try to get network info
|
||||
try:
|
||||
cmd = [VBOX, 'guestproperty', 'get', current_host, netinfo]
|
||||
if args:
|
||||
cmd.append(args)
|
||||
x = Popen(cmd, stdout=PIPE)
|
||||
ipinfo = x.stdout.read()
|
||||
if 'Value' in ipinfo:
|
||||
a, ip = ipinfo.split(':', 1)
|
||||
self.inventory.set_variable(current_host, 'ansible_host', ip.strip())
|
||||
except:
|
||||
pass
|
||||
|
||||
# found groups
|
||||
elif k == 'Groups':
|
||||
for group in v.split('/'):
|
||||
if group:
|
||||
self.inventory.add_group(group)
|
||||
self.inventory.add_child(group, current_host)
|
||||
continue
|
||||
|
||||
else:
|
||||
# found vars, accumulate in hostvars for clean inventory set
|
||||
pref_k = 'vbox_' + k.strip().replace(' ', '_')
|
||||
if k.startswith(' '):
|
||||
if prevkey not in hostvars[current_host]:
|
||||
hostvars[current_host][prevkey] = {}
|
||||
hostvars[current_host][prevkey][pref_k] = v
|
||||
else:
|
||||
if v != '':
|
||||
hostvars[current_host][pref_k] = v
|
||||
|
||||
prevkey = pref_k
|
||||
|
||||
# set vars in inventory from hostvars
|
||||
for host in hostvars:
|
||||
|
||||
# create composite vars
|
||||
if data.get('compose') and isinstance(data['compose'], dict):
|
||||
for varname in data['compose']:
|
||||
hostvars[host][varname] = self._compose(data['compose'][varname], hostvars[host])
|
||||
|
||||
# actually update inventory
|
||||
for key in hostvars[host]:
|
||||
self.inventory.set_variable(host, key, hostvars[host][key])
|
181
lib/ansible/plugins/inventory/yaml.py
Normal file
181
lib/ansible/plugins/inventory/yaml.py
Normal file
|
@ -0,0 +1,181 @@
|
|||
# Copyright 2017 RedHat, inc
|
||||
#
|
||||
# This file is part of Ansible
|
||||
#
|
||||
# Ansible is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Ansible is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
#############################################
|
||||
'''
|
||||
DOCUMENTATION:
|
||||
inventory: yaml
|
||||
version_added: "2.4"
|
||||
short_description: Uses a specifically YAML file as inventory source.
|
||||
description:
|
||||
- YAML based inventory, starts with the 'all' group and has hosts/vars/children entries.
|
||||
- Host entries can have sub-entries defined, which will be treated as variables.
|
||||
- Vars entries are normal group vars.
|
||||
- Children are 'child groups', which can also have their own vars/hosts/children and so on.
|
||||
- File MUST have a valid extension: yaml, yml, json.
|
||||
notes:
|
||||
- It takes the place of the previously hardcoded YAML inventory.
|
||||
- To function it requires being whitelisted in configuration.
|
||||
options:
|
||||
_yaml_extensions:
|
||||
description: list of 'valid' extensions for files containing YAML
|
||||
EXAMPLES:
|
||||
all: # keys must be unique, i.e. only one 'hosts' per group
|
||||
hosts:
|
||||
test1:
|
||||
test2:
|
||||
var1: value1
|
||||
vars:
|
||||
group_var1: value2
|
||||
children: # key order does not matter, indentation does
|
||||
other_group:
|
||||
children:
|
||||
group_x:
|
||||
hosts:
|
||||
test5
|
||||
vars:
|
||||
g2_var2: value3
|
||||
hosts:
|
||||
test4:
|
||||
ansible_host: 127.0.0.1
|
||||
last_group:
|
||||
hosts:
|
||||
test1 # same host as above, additional group membership
|
||||
vars:
|
||||
last_var: MYVALUE
|
||||
'''
|
||||
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
import re
|
||||
import os
|
||||
|
||||
from ansible import constants as C
|
||||
from ansible.errors import AnsibleParserError
|
||||
from ansible.module_utils.six import string_types
|
||||
from ansible.module_utils._text import to_bytes, to_text
|
||||
from ansible.parsing.utils.addresses import parse_address
|
||||
from ansible.plugins.inventory import BaseFileInventoryPlugin, detect_range, expand_hostname_range
|
||||
|
||||
class InventoryModule(BaseFileInventoryPlugin):
|
||||
|
||||
NAME = 'yaml'
|
||||
|
||||
def __init__(self):
|
||||
|
||||
super(InventoryModule, self).__init__()
|
||||
self.patterns = {}
|
||||
|
||||
self._compile_patterns()
|
||||
|
||||
def verify_file(self, path):
|
||||
|
||||
valid = False
|
||||
b_path = to_bytes(path)
|
||||
if super(InventoryModule, self).verify_file(b_path):
|
||||
file_name, ext = os.path.splitext(b_path)
|
||||
if ext and ext in C.YAML_FILENAME_EXTENSIONS:
|
||||
valid = True
|
||||
return valid
|
||||
|
||||
def parse(self, inventory, loader, path, cache=True):
|
||||
''' parses the inventory file '''
|
||||
|
||||
super(InventoryModule, self).parse(inventory, loader, path)
|
||||
|
||||
try:
|
||||
data = self.loader.load_from_file(path)
|
||||
except Exception as e:
|
||||
raise AnsibleParserError(e)
|
||||
|
||||
if not data:
|
||||
return False
|
||||
|
||||
# We expect top level keys to correspond to groups, iterate over them
|
||||
# to get host, vars and subgroups (which we iterate over recursivelly)
|
||||
if isinstance(data, dict):
|
||||
for group_name in data:
|
||||
self._parse_group(group_name, data[group_name])
|
||||
else:
|
||||
raise AnsibleParserError("Invalid data from file, expected dictionary and got:\n\n%s" % data)
|
||||
|
||||
def _parse_group(self, group, group_data):
|
||||
|
||||
if self.patterns['groupname'].match(group):
|
||||
|
||||
self.inventory.add_group(group)
|
||||
|
||||
if isinstance(group_data, dict):
|
||||
#make sure they are dicts
|
||||
for section in ['vars', 'children', 'hosts']:
|
||||
if section in group_data and isinstance(group_data[section], string_types):
|
||||
group_data[section] = {group_data[section]: None}
|
||||
|
||||
if 'vars' in group_data:
|
||||
for var in group_data['vars']:
|
||||
self.inventory.set_variable(group, var, group_data['vars'][var])
|
||||
|
||||
if 'children' in group_data:
|
||||
for subgroup in group_data['children']:
|
||||
self._parse_group(subgroup, group_data['children'][subgroup])
|
||||
self.inventory.add_child(group, subgroup)
|
||||
|
||||
if 'hosts' in group_data:
|
||||
for host_pattern in group_data['hosts']:
|
||||
hosts, port = self._parse_host(host_pattern)
|
||||
self.populate_host_vars(hosts, group_data['hosts'][host_pattern], group, port)
|
||||
else:
|
||||
self.display.warning("Skipping '%s' as this is not a valid group name" % group)
|
||||
|
||||
def _parse_host(self, host_pattern):
|
||||
'''
|
||||
Each host key can be a pattern, try to process it and add variables as needed
|
||||
'''
|
||||
(hostnames, port) = self._expand_hostpattern(host_pattern)
|
||||
|
||||
return hostnames, port
|
||||
|
||||
def _expand_hostpattern(self, hostpattern):
|
||||
'''
|
||||
Takes a single host pattern and returns a list of hostnames and an
|
||||
optional port number that applies to all of them.
|
||||
'''
|
||||
# Can the given hostpattern be parsed as a host with an optional port
|
||||
# specification?
|
||||
|
||||
try:
|
||||
(pattern, port) = parse_address(hostpattern, allow_ranges=True)
|
||||
except:
|
||||
# not a recognizable host pattern
|
||||
pattern = hostpattern
|
||||
port = None
|
||||
|
||||
# Once we have separated the pattern, we expand it into list of one or
|
||||
# more hostnames, depending on whether it contains any [x:y] ranges.
|
||||
|
||||
if detect_range(pattern):
|
||||
hostnames = expand_hostname_range(pattern)
|
||||
else:
|
||||
hostnames = [pattern]
|
||||
|
||||
return (hostnames, port)
|
||||
|
||||
def _compile_patterns(self):
|
||||
'''
|
||||
Compiles the regular expressions required to parse the inventory and stores them in self.patterns.
|
||||
'''
|
||||
self.patterns['groupname'] = re.compile( r'''^[A-Za-z_][A-Za-z0-9_]*$''')
|
|
@ -20,7 +20,7 @@ from __future__ import (absolute_import, division, print_function)
|
|||
__metaclass__ = type
|
||||
|
||||
from ansible.plugins.lookup import LookupBase
|
||||
from ansible.inventory import Inventory
|
||||
from ansible.inventory.manager import split_host_pattern, order_patterns
|
||||
|
||||
class LookupModule(LookupBase):
|
||||
|
||||
|
@ -42,7 +42,7 @@ class LookupModule(LookupBase):
|
|||
host_list = []
|
||||
|
||||
for term in terms:
|
||||
patterns = Inventory.order_patterns(Inventory.split_host_pattern(term))
|
||||
patterns = order_patterns(split_host_pattern(term))
|
||||
|
||||
for p in patterns:
|
||||
that = self.get_hosts(variables, p)
|
||||
|
|
|
@ -33,7 +33,6 @@ from ansible.executor import action_write_locks
|
|||
from ansible.executor.process.worker import WorkerProcess
|
||||
from ansible.executor.task_result import TaskResult
|
||||
from ansible.inventory.host import Host
|
||||
from ansible.inventory.group import Group
|
||||
from ansible.module_utils.six.moves import queue as Queue
|
||||
from ansible.module_utils.six import iteritems, string_types
|
||||
from ansible.module_utils._text import to_text
|
||||
|
@ -43,7 +42,8 @@ from ansible.playbook.task_include import TaskInclude
|
|||
from ansible.playbook.role_include import IncludeRole
|
||||
from ansible.plugins import action_loader, connection_loader, filter_loader, lookup_loader, module_loader, test_loader
|
||||
from ansible.template import Templar
|
||||
from ansible.vars import combine_vars, strip_internal_keys
|
||||
from ansible.utils.vars import combine_vars
|
||||
from ansible.vars.manager import strip_internal_keys
|
||||
|
||||
|
||||
try:
|
||||
|
@ -260,9 +260,10 @@ class StrategyBase:
|
|||
ret_results = []
|
||||
|
||||
def get_original_host(host_name):
|
||||
#FIXME: this should not need x2 _inventory
|
||||
host_name = to_text(host_name)
|
||||
if host_name in self._inventory._hosts_cache:
|
||||
return self._inventory._hosts_cache[host_name]
|
||||
if host_name in self._inventory.hosts:
|
||||
return self._inventory.hosts[host_name]
|
||||
else:
|
||||
return self._inventory.get_host(host_name)
|
||||
|
||||
|
@ -270,7 +271,7 @@ class StrategyBase:
|
|||
for handler_block in handler_blocks:
|
||||
for handler_task in handler_block.block:
|
||||
if handler_task.name:
|
||||
handler_vars = self._variable_manager.get_vars(loader=self._loader, play=iterator._play, task=handler_task)
|
||||
handler_vars = self._variable_manager.get_vars(play=iterator._play, task=handler_task)
|
||||
templar = Templar(loader=self._loader, variables=handler_vars)
|
||||
try:
|
||||
# first we check with the full result of get_name(), which may
|
||||
|
@ -304,7 +305,7 @@ class StrategyBase:
|
|||
if target_handler:
|
||||
if isinstance(target_handler, (TaskInclude, IncludeRole)):
|
||||
try:
|
||||
handler_vars = self._variable_manager.get_vars(loader=self._loader, play=iterator._play, task=target_handler)
|
||||
handler_vars = self._variable_manager.get_vars(play=iterator._play, task=target_handler)
|
||||
templar = Templar(loader=self._loader, variables=handler_vars)
|
||||
target_handler_name = templar.template(target_handler.name)
|
||||
if target_handler_name == handler_name:
|
||||
|
@ -589,50 +590,29 @@ class StrategyBase:
|
|||
Helper function to add a new host to inventory based on a task result.
|
||||
'''
|
||||
|
||||
host_name = host_info.get('host_name')
|
||||
if host_info:
|
||||
host_name = host_info.get('host_name')
|
||||
|
||||
# Check if host in inventory, add if not
|
||||
new_host = self._inventory.get_host(host_name)
|
||||
if not new_host:
|
||||
new_host = Host(name=host_name)
|
||||
self._inventory._hosts_cache[host_name] = new_host
|
||||
self._inventory.get_host_vars(new_host)
|
||||
# Check if host in inventory, add if not
|
||||
if not host_name in self._inventory.hosts:
|
||||
self._inventory.add_host(host_name, 'all')
|
||||
new_host = self._inventory.hosts.get(host_name)
|
||||
|
||||
allgroup = self._inventory.get_group('all')
|
||||
allgroup.add_host(new_host)
|
||||
# Set/update the vars for this host
|
||||
new_host.vars = combine_vars(new_host.get_vars(), host_info.get('host_vars', dict()))
|
||||
|
||||
# Set/update the vars for this host
|
||||
new_host.vars = combine_vars(new_host.vars, self._inventory.get_host_vars(new_host))
|
||||
new_host.vars = combine_vars(new_host.vars, host_info.get('host_vars', dict()))
|
||||
new_groups = host_info.get('groups', [])
|
||||
for group_name in new_groups:
|
||||
if group_name not in self._inventory.groups:
|
||||
self._inventory.add_group(group_name)
|
||||
new_group = self._inventory.groups[group_name]
|
||||
new_group.add_host(self._inventory.hosts[host_name])
|
||||
|
||||
new_groups = host_info.get('groups', [])
|
||||
for group_name in new_groups:
|
||||
if not self._inventory.get_group(group_name):
|
||||
new_group = Group(group_name)
|
||||
self._inventory.add_group(new_group)
|
||||
self._inventory.get_group_vars(new_group)
|
||||
new_group.vars = self._inventory.get_group_variables(group_name)
|
||||
else:
|
||||
new_group = self._inventory.get_group(group_name)
|
||||
# clear pattern caching completely since it's unpredictable what patterns may have referenced the group
|
||||
self._inventory.clear_pattern_cache()
|
||||
|
||||
new_group.add_host(new_host)
|
||||
|
||||
# add this host to the group cache
|
||||
if self._inventory.groups is not None:
|
||||
if group_name in self._inventory.groups:
|
||||
if new_host not in self._inventory.get_group(group_name).hosts:
|
||||
self._inventory.get_group(group_name).hosts.append(new_host.name)
|
||||
|
||||
# clear pattern caching completely since it's unpredictable what
|
||||
# patterns may have referenced the group
|
||||
self._inventory.clear_pattern_cache()
|
||||
|
||||
# clear cache of group dict, which is used in magic host variables
|
||||
self._inventory.clear_group_dict_cache()
|
||||
|
||||
# also clear the hostvar cache entry for the given play, so that
|
||||
# the new hosts are available if hostvars are referenced
|
||||
self._variable_manager.invalidate_hostvars_cache(play=iterator._play)
|
||||
# reconcile inventory, ensures inventory rules are followed
|
||||
self._inventory.reconcile_inventory()
|
||||
|
||||
def _add_group(self, host, result_item):
|
||||
'''
|
||||
|
@ -645,28 +625,26 @@ class StrategyBase:
|
|||
# the host here is from the executor side, which means it was a
|
||||
# serialized/cloned copy and we'll need to look up the proper
|
||||
# host object from the master inventory
|
||||
real_host = self._inventory.get_host(host.name)
|
||||
|
||||
real_host = self._inventory.hosts[host.name]
|
||||
group_name = result_item.get('add_group')
|
||||
new_group = self._inventory.get_group(group_name)
|
||||
if not new_group:
|
||||
# create the new group and add it to inventory
|
||||
new_group = Group(name=group_name)
|
||||
self._inventory.add_group(new_group)
|
||||
new_group.vars = self._inventory.get_group_vars(new_group)
|
||||
|
||||
# and add the group to the proper hierarchy
|
||||
allgroup = self._inventory.get_group('all')
|
||||
allgroup.add_child_group(new_group)
|
||||
if group_name not in self._inventory.groups:
|
||||
# create the new group and add it to inventory
|
||||
self._inventory.add_group(group_name)
|
||||
changed = True
|
||||
group = self._inventory.groups[group_name]
|
||||
|
||||
if real_host.name not in group.get_hosts():
|
||||
group.add_host(real_host)
|
||||
changed = True
|
||||
|
||||
if group_name not in host.get_groups():
|
||||
new_group.add_host(real_host)
|
||||
real_host.add_group(group)
|
||||
changed = True
|
||||
|
||||
if changed:
|
||||
# clear cache of group dict, which is used in magic host variables
|
||||
self._inventory.clear_group_dict_cache()
|
||||
self._inventory.clear_pattern_cache()
|
||||
self._inventory.reconcile_inventory()
|
||||
|
||||
return changed
|
||||
|
||||
|
@ -780,7 +758,7 @@ class StrategyBase:
|
|||
host_results = []
|
||||
for host in notified_hosts:
|
||||
if not handler.has_triggered(host) and (not iterator.is_failed(host) or play_context.force_handlers):
|
||||
task_vars = self._variable_manager.get_vars(loader=self._loader, play=iterator._play, host=host, task=handler)
|
||||
task_vars = self._variable_manager.get_vars(play=iterator._play, host=host, task=handler)
|
||||
self.add_tqm_variables(task_vars, play=iterator._play)
|
||||
self._queue_task(host, handler, task_vars, play_context)
|
||||
if run_once:
|
||||
|
@ -867,7 +845,7 @@ class StrategyBase:
|
|||
# on a meta task that doesn't support them
|
||||
|
||||
def _evaluate_conditional(h):
|
||||
all_vars = self._variable_manager.get_vars(loader=self._loader, play=iterator._play, host=h, task=task)
|
||||
all_vars = self._variable_manager.get_vars(play=iterator._play, host=h, task=task)
|
||||
templar = Templar(loader=self._loader, variables=all_vars)
|
||||
return task.evaluate_conditional(templar, all_vars)
|
||||
|
||||
|
|
|
@ -115,7 +115,7 @@ class StrategyModule(StrategyBase):
|
|||
action = None
|
||||
|
||||
display.debug("getting variables")
|
||||
task_vars = self._variable_manager.get_vars(loader=self._loader, play=iterator._play, host=host, task=task)
|
||||
task_vars = self._variable_manager.get_vars(play=iterator._play, host=host, task=task)
|
||||
self.add_tqm_variables(task_vars, play=iterator._play)
|
||||
templar = Templar(loader=self._loader, variables=task_vars)
|
||||
display.debug("done getting variables")
|
||||
|
@ -201,7 +201,7 @@ class StrategyModule(StrategyBase):
|
|||
continue
|
||||
|
||||
for new_block in new_blocks:
|
||||
task_vars = self._variable_manager.get_vars(loader=self._loader, play=iterator._play, task=included_file._task)
|
||||
task_vars = self._variable_manager.get_vars(play=iterator._play, task=included_file._task)
|
||||
final_block = new_block.filter_tagged_tasks(play_context, task_vars)
|
||||
for host in hosts_left:
|
||||
if host in included_file._hosts:
|
||||
|
|
|
@ -245,7 +245,7 @@ class StrategyModule(StrategyBase):
|
|||
break
|
||||
|
||||
display.debug("getting variables")
|
||||
task_vars = self._variable_manager.get_vars(loader=self._loader, play=iterator._play, host=host, task=task)
|
||||
task_vars = self._variable_manager.get_vars(play=iterator._play, host=host, task=task)
|
||||
self.add_tqm_variables(task_vars, play=iterator._play)
|
||||
templar = Templar(loader=self._loader, variables=task_vars)
|
||||
display.debug("done getting variables")
|
||||
|
@ -355,7 +355,6 @@ class StrategyModule(StrategyBase):
|
|||
display.debug("iterating over new_blocks loaded from include file")
|
||||
for new_block in new_blocks:
|
||||
task_vars = self._variable_manager.get_vars(
|
||||
loader=self._loader,
|
||||
play=iterator._play,
|
||||
task=included_file._task,
|
||||
)
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
# (c) 2012-2014, Michael DeHaan <michael.dehaan@gmail.com>
|
||||
# (c) 2014, Serge van Ginderachter <serge@vanginderachter.be>
|
||||
#
|
||||
# This file is part of Ansible
|
||||
#
|
||||
|
@ -14,8 +15,28 @@
|
|||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
# Make coding more python3-ish
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
from ansible.utils.path import basedir
|
||||
|
||||
try:
|
||||
from __main__ import display
|
||||
except ImportError:
|
||||
from ansible.utils.display import Display
|
||||
display = Display()
|
||||
|
||||
class BaseVarsPlugin(object):
|
||||
|
||||
"""
|
||||
Loads variables for groups and/or hosts
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
""" constructor """
|
||||
self._display = display
|
||||
|
||||
def get_vars(self, loader, path, entities):
|
||||
""" Gets variables. """
|
||||
self._basedir = basedir(path)
|
||||
|
||||
|
|
107
lib/ansible/plugins/vars/host_group_vars.py
Normal file
107
lib/ansible/plugins/vars/host_group_vars.py
Normal file
|
@ -0,0 +1,107 @@
|
|||
# Copyright 2017 RedHat, inc
|
||||
#
|
||||
# This file is part of Ansible
|
||||
#
|
||||
# Ansible is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Ansible is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
#############################################
|
||||
'''
|
||||
DOCUMENTATION:
|
||||
vars: host_group_vars
|
||||
version_added: "2.4"
|
||||
short_description: In charge of loading group_vars and host_vars
|
||||
description:
|
||||
- Loads YAML vars into corresponding groups/hosts in group_vars/ and host_vars/ directories.
|
||||
- Files are restricted by extension to one of .yaml, .json, .yml or no extension.
|
||||
- Only applies to inventory sources that are existing paths.
|
||||
notes:
|
||||
- It takes the place of the previously hardcoded group_vars/host_vars loading.
|
||||
'''
|
||||
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
import os
|
||||
from ansible import constants as C
|
||||
from ansible.errors import AnsibleParserError
|
||||
from ansible.module_utils._text import to_bytes, to_text
|
||||
from ansible.plugins.vars import BaseVarsPlugin
|
||||
from ansible.utils.path import basedir
|
||||
from ansible.inventory.host import Host
|
||||
from ansible.inventory.group import Group
|
||||
from ansible.utils.vars import combine_vars
|
||||
|
||||
|
||||
class VarsModule(BaseVarsPlugin):
|
||||
|
||||
def get_vars(self, loader, path, entities):
|
||||
''' parses the inventory file '''
|
||||
|
||||
if not isinstance(entities, list):
|
||||
entities = [entities]
|
||||
|
||||
super(VarsModule, self).get_vars(loader, path, entities)
|
||||
|
||||
data = {}
|
||||
for entity in entities:
|
||||
if isinstance(entity, Host):
|
||||
subdir = 'host_vars'
|
||||
elif isinstance(entity, Group):
|
||||
subdir = 'group_vars'
|
||||
else:
|
||||
raise AnsibleParserError("Supplied entity must be Host or Group, got %s instead" % (type(entity)))
|
||||
|
||||
try:
|
||||
# load vars
|
||||
opath = os.path.realpath(os.path.join(self._basedir, subdir))
|
||||
b_opath = to_bytes(opath)
|
||||
# no need to do much if path does not exist for basedir
|
||||
if os.path.exists(b_opath):
|
||||
if os.path.isdir(b_opath):
|
||||
self._display.debug("\tprocessing dir %s" % opath)
|
||||
for found in self._find_vars_files(opath, entity.name):
|
||||
self._display.debug("READING %s" % found)
|
||||
data = combine_vars(data, loader.load_from_file(found, cache=True, unsafe=True))
|
||||
else:
|
||||
self._display.warning("Found %s that is not a directory, skipping: %s" % (subdir, opath))
|
||||
|
||||
except Exception as e:
|
||||
raise AnsibleParserError(to_text(e))
|
||||
return data
|
||||
|
||||
def _find_vars_files(self, path, name):
|
||||
""" Find {group,host}_vars files """
|
||||
|
||||
b_path = to_bytes(os.path.join(path, name))
|
||||
found = []
|
||||
for ext in C.YAML_FILENAME_EXTENSIONS + ['']:
|
||||
|
||||
if '.' in ext:
|
||||
full_path = b_path + to_bytes(ext)
|
||||
elif ext:
|
||||
full_path = b'.'.join([b_path, to_bytes(ext)])
|
||||
else:
|
||||
full_path = b_path
|
||||
|
||||
if os.path.exists(full_path):
|
||||
self._display.debug("\tfound %s" % to_text(full_path))
|
||||
if os.path.isdir(full_path):
|
||||
# matched dir name, so use all files included recursively
|
||||
for spath in os.listdir(full_path):
|
||||
if os.path.isdir(spath):
|
||||
found.extend(self._find_vars_files(spath, name))
|
||||
else:
|
||||
found.append(spath)
|
||||
else:
|
||||
found.append(full_path)
|
||||
return found
|
Loading…
Add table
Add a link
Reference in a new issue