All lookups ported to config system (#74108)

* all lookups to support config system

 - added get_options to get full dict with all opts
 - fixed tests to match new error messages
 - kept inline string k=v parsing methods for backwards compat
 - placeholder depredation for inline string k=v parsing
 - updated tests and examples to also show new way
 - refactored and added comments to most custom k=v parsing
 - added missing docs for template_vars to template
 - normalized error messages and exception types
 - fixed constants default
 - better details value errors

Co-authored-by: Felix Fontein <felix@fontein.de>
This commit is contained in:
Brian Coca 2021-04-13 15:52:42 -04:00 committed by GitHub
parent 4819e9301b
commit 84e473a26e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 270 additions and 149 deletions

View file

@ -5,20 +5,23 @@
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
import os
import re
from ast import literal_eval
from jinja2 import Template
from string import ascii_letters, digits
from ansible.config.manager import ConfigManager, ensure_type, get_ini_config_value
from ansible.config.manager import ConfigManager, ensure_type
from ansible.module_utils._text import to_text
from ansible.module_utils.common.collections import Sequence
from ansible.module_utils.parsing.convert_bool import boolean, BOOLEANS_TRUE
from ansible.module_utils.parsing.convert_bool import BOOLEANS_TRUE
from ansible.module_utils.six import string_types
from ansible.release import __version__
from ansible.utils.fqcn import add_internal_fqcns
# 4 versions above current
default_deprecated = to_text(float('.'.join(__version__.split('.')[0:2])) + 0.04)
def _warning(msg):
''' display is not guaranteed here, nor it being the full class, but try anyways, fallback to sys.stderr.write '''
@ -30,7 +33,7 @@ def _warning(msg):
sys.stderr.write(' [WARNING] %s\n' % (msg))
def _deprecated(msg, version='2.8'):
def _deprecated(msg, version=default_deprecated):
''' display is not guaranteed here, nor it being the full class, but try anyways, fallback to sys.stderr.write '''
try:
from ansible.utils.display import Display

View file

@ -61,6 +61,13 @@ class AnsiblePlugin(with_metaclass(ABCMeta, object)):
self.set_option(option, option_value)
return self._options.get(option)
def get_options(self, hostvars=None):
options = {}
defs = C.config.get_configuration_definitions(plugin_type=get_plugin_class(self), name=self._load_name)
for option in defs:
options[option] = self.get_option(option, hostvars=hostvars)
return options
def set_option(self, option, value):
self._options[option] = value

View file

@ -123,3 +123,8 @@ class LookupBase(AnsiblePlugin):
self._display.warning("Unable to find '%s' in expected paths (use -vvvvv to see paths)" % needle)
return result
def _deprecate_inline_kv(self):
# TODO: place holder to deprecate in future version allowing for long transition period
# self._display.deprecated('Passing inline k=v values embeded in a string to this lookup. Use direct ,k=v, k2=v2 syntax instead.', version='2.18')
pass

View file

@ -132,6 +132,7 @@ class LookupModule(LookupBase):
raise AnsibleOptionsError('"on_missing" must be a string and one of "error", "warn" or "skip", not %s' % missing)
ret = []
for term in terms:
if not isinstance(term, string_types):
raise AnsibleOptionsError('Invalid setting identifier, "%s" is not a string, its a %s' % (term, type(term)))

View file

@ -19,7 +19,6 @@ DOCUMENTATION = """
default: "1"
default:
description: what to return if the value is not found in the file.
default: ''
delimiter:
description: field separator in the file, for a tab you can specify C(TAB) or C(\\t).
default: TAB
@ -40,20 +39,22 @@ DOCUMENTATION = """
EXAMPLES = """
- name: Match 'Li' on the first column, return the second column (0 based index)
debug: msg="The atomic number of Lithium is {{ lookup('csvfile', 'Li file=elements.csv delimiter=,') }}"
debug: msg="The atomic number of Lithium is {{ lookup('csvfile', 'Li', file='elements.csv', delimiter=',') }}"
- name: msg="Match 'Li' on the first column, but return the 3rd column (columns start counting after the match)"
debug: msg="The atomic mass of Lithium is {{ lookup('csvfile', 'Li file=elements.csv delimiter=, col=2') }}"
debug: msg="The atomic mass of Lithium is {{ lookup('csvfile', 'Li', file='elements.csv', delimiter=',', col=2) }}"
- name: Define Values From CSV File
- name: Define Values From CSV File, this reads file in one go, but you could also use col= to read each in it's own lookup.
set_fact:
loop_ip: "{{ lookup('csvfile', bgp_neighbor_ip +' file=bgp_neighbors.csv delimiter=, col=1') }}"
int_ip: "{{ lookup('csvfile', bgp_neighbor_ip +' file=bgp_neighbors.csv delimiter=, col=2') }}"
int_mask: "{{ lookup('csvfile', bgp_neighbor_ip +' file=bgp_neighbors.csv delimiter=, col=3') }}"
int_name: "{{ lookup('csvfile', bgp_neighbor_ip +' file=bgp_neighbors.csv delimiter=, col=4') }}"
local_as: "{{ lookup('csvfile', bgp_neighbor_ip +' file=bgp_neighbors.csv delimiter=, col=5') }}"
neighbor_as: "{{ lookup('csvfile', bgp_neighbor_ip +' file=bgp_neighbors.csv delimiter=, col=6') }}"
neigh_int_ip: "{{ lookup('csvfile', bgp_neighbor_ip +' file=bgp_neighbors.csv delimiter=, col=7') }}"
loop_ip: "{{ csvline[0] }}"
int_ip: "{{ csvline[1] }}"
int_mask: "{{ csvline[2] }}"
int_name: "{{ csvline[3] }}"
local_as: "{{ csvline[4] }}"
neighbor_as: "{{ csvline[5] }}"
neigh_int_ip: "{{ csvline[6] }}"
vars:
csvline = "{{ lookup('csvfile', bgp_neighbor_ip, file='bgp_neighbors.csv', delimiter=',') }}"
delegate_to: localhost
"""
@ -121,7 +122,7 @@ class LookupModule(LookupBase):
def read_csv(self, filename, key, delimiter, encoding='utf-8', dflt=None, col=1):
try:
f = open(filename, 'rb')
f = open(to_bytes(filename), 'rb')
creader = CSVReader(f, delimiter=to_native(delimiter), encoding=encoding)
for row in creader:
@ -136,6 +137,11 @@ class LookupModule(LookupBase):
ret = []
self.set_options(var_options=variables, direct=kwargs)
# populate options
paramvals = self.get_options()
for term in terms:
kv = parse_kv(term)
@ -144,25 +150,21 @@ class LookupModule(LookupBase):
key = kv['_raw_params']
paramvals = {
'col': "1", # column to return
'default': None,
'delimiter': "TAB",
'file': 'ansible.csv',
'encoding': 'utf-8',
}
# parameters specified?
# parameters override per term using k/v
try:
for name, value in kv.items():
if name == '_raw_params':
continue
if name not in paramvals:
raise AnsibleAssertionError('%s not in paramvals' % name)
raise AnsibleAssertionError('%s is not a valid option' % name)
self._deprecate_inline_kv()
paramvals[name] = value
except (ValueError, AssertionError) as e:
raise AnsibleError(e)
# default is just placeholder for real tab
if paramvals['delimiter'] == 'TAB':
paramvals['delimiter'] = "\t"
@ -174,4 +176,5 @@ class LookupModule(LookupBase):
ret.append(v)
else:
ret.append(var)
return ret

View file

@ -62,7 +62,7 @@ class LookupModule(LookupBase):
def run(self, terms, variables=None, **kwargs):
# FIXME: can remove once with_ special case is removed
# NOTE: can remove if with_ is removed
if not isinstance(terms, list):
terms = [terms]

View file

@ -62,6 +62,7 @@ class LookupModule(LookupBase):
def run(self, terms, variables=None, **kwargs):
ret = []
self.set_options(var_options=variables, direct=kwargs)
for term in terms:
display.debug("File lookup term: %s" % term)
@ -73,9 +74,9 @@ class LookupModule(LookupBase):
if lookupfile:
b_contents, show_data = self._loader._get_file_contents(lookupfile)
contents = to_text(b_contents, errors='surrogate_or_strict')
if kwargs.get('lstrip', False):
if self.get_option('lstrip'):
contents = contents.lstrip()
if kwargs.get('rstrip', True):
if self.get_option('rstrip'):
contents = contents.rstrip()
ret.append(contents)
else:

View file

@ -23,8 +23,12 @@ DOCUMENTATION = """
description: list of file names
files:
description: list of file names
type: list
default: []
paths:
description: list of paths in which to look for the files
type: list
default: []
skip:
type: boolean
default: False
@ -106,71 +110,100 @@ import os
from jinja2.exceptions import UndefinedError
from ansible.errors import AnsibleFileNotFound, AnsibleLookupError, AnsibleUndefinedVariable
from ansible.errors import AnsibleLookupError, AnsibleUndefinedVariable
from ansible.module_utils.common._collections_compat import Mapping, Sequence
from ansible.module_utils.six import string_types
from ansible.module_utils.parsing.convert_bool import boolean
from ansible.plugins.lookup import LookupBase
def _split_on(terms, spliters=','):
# TODO: fix as it does not allow spaces in names
termlist = []
if isinstance(terms, string_types):
for spliter in spliters:
terms = terms.replace(spliter, ' ')
termlist = terms.split(' ')
else:
# added since options will already listify
for t in terms:
termlist.extend(_split_on(t, spliters))
return termlist
class LookupModule(LookupBase):
def run(self, terms, variables, **kwargs):
anydict = False
skip = False
for term in terms:
if isinstance(term, dict):
anydict = True
def _process_terms(self, terms, variables, kwargs):
total_search = []
if anydict:
for term in terms:
if isinstance(term, dict):
skip = False
files = term.get('files', [])
paths = term.get('paths', [])
skip = boolean(term.get('skip', False), strict=False)
# can use a dict instead of list item to pass inline config
for term in terms:
if isinstance(term, Mapping):
self.set_options(var_options=variables, direct=term)
elif isinstance(term, string_types):
self.set_options(var_options=variables, direct=kwargs)
elif isinstance(term, Sequence):
partial, skip = self._process_terms(term, variables, kwargs)
total_search.extend(partial)
continue
else:
raise AnsibleLookupError("Invalid term supplied, can handle string, mapping or list of strings but got: %s for %s" % (type(term), term))
filelist = files
if isinstance(files, string_types):
files = files.replace(',', ' ')
files = files.replace(';', ' ')
filelist = files.split(' ')
files = self.get_option('files')
paths = self.get_option('paths')
pathlist = paths
if paths:
if isinstance(paths, string_types):
paths = paths.replace(',', ' ')
paths = paths.replace(':', ' ')
paths = paths.replace(';', ' ')
pathlist = paths.split(' ')
# NOTE: this is used as 'global' but can be set many times?!?!?
skip = self.get_option('skip')
if not pathlist:
total_search = filelist
else:
for path in pathlist:
for fn in filelist:
f = os.path.join(path, fn)
total_search.append(f)
else:
total_search.append(term)
else:
total_search = self._flatten(terms)
# magic extra spliting to create lists
filelist = _split_on(files, ',;')
pathlist = _split_on(paths, ',:;')
# create search structure
if pathlist:
for path in pathlist:
for fn in filelist:
f = os.path.join(path, fn)
total_search.append(f)
elif filelist:
# NOTE: this seems wrong, should be 'extend' as any option/entry can clobber all
total_search = filelist
else:
total_search.append(term)
return total_search, skip
def run(self, terms, variables, **kwargs):
total_search, skip = self._process_terms(terms, variables, kwargs)
# NOTE: during refactor noticed that the 'using a dict' as term
# is designed to only work with 'one' otherwise inconsistencies will appear.
# see other notes below.
# actually search
subdir = getattr(self, '_subdir', 'files')
path = None
for fn in total_search:
try:
fn = self._templar.template(fn)
except (AnsibleUndefinedVariable, UndefinedError):
continue
# get subdir if set by task executor, default to files otherwise
subdir = getattr(self, '_subdir', 'files')
path = None
path = self.find_file_in_search_path(variables, subdir, fn, ignore_missing=True)
# exit if we find one!
if path is not None:
return [path]
# if we get here, no file was found
if skip:
# NOTE: global skip wont matter, only last 'skip' value in dict term
return []
raise AnsibleLookupError("No file was found when using first_found. Use errors='ignore' to allow this task to be skipped if no "
"files are found")
raise AnsibleLookupError("No file was found when using first_found. Use errors='ignore' to allow this task to be skipped if no files are found")

View file

@ -23,7 +23,7 @@ DOCUMENTATION = """
choices: ['ini', 'properties']
file:
description: Name of the file to load.
default: ansible.ini
default: 'ansible.ini'
section:
default: global
description: Section where to lookup the key.
@ -40,16 +40,15 @@ DOCUMENTATION = """
"""
EXAMPLES = """
- debug: msg="User in integration is {{ lookup('ini', 'user section=integration file=users.ini') }}"
- debug: msg="User in integration is {{ lookup('ini', 'user', section='integration', file='users.ini') }}"
- debug: msg="User in production is {{ lookup('ini', 'user section=production file=users.ini') }}"
- debug: msg="User in production is {{ lookup('ini', 'user', section='production', file='users.ini') }}"
- debug: msg="user.name is {{ lookup('ini', 'user.name type=properties file=user.properties') }}"
- debug: msg="user.name is {{ lookup('ini', 'user.name', type='properties', file='user.properties') }}"
- debug:
msg: "{{ item }}"
with_ini:
- '.* section=section1 file=test.ini re=True'
loop: "{{q('ini', '.*', section='section1', file='test.ini', re=True)}}"
"""
RETURN = """
@ -59,37 +58,48 @@ _raw:
type: list
elements: str
"""
import os
import re
from io import StringIO
from ansible.errors import AnsibleError, AnsibleAssertionError
from io import StringIO
from collections import defaultdict
from ansible.errors import AnsibleLookupError
from ansible.module_utils.six.moves import configparser
from ansible.module_utils._text import to_bytes, to_text
from ansible.module_utils._text import to_bytes, to_text, to_native
from ansible.module_utils.common._collections_compat import MutableSequence
from ansible.plugins.lookup import LookupBase
def _parse_params(term):
def _parse_params(term, paramvals):
'''Safely split parameter term to preserve spaces'''
keys = ['key', 'type', 'section', 'file', 're', 'default', 'encoding']
params = {}
for k in keys:
params[k] = ''
# TODO: deprecate this method
valid_keys = paramvals.keys()
params = defaultdict(lambda: '')
thiskey = 'key'
# TODO: check kv_parser to see if it can handle spaces this same way
keys = []
thiskey = 'key' # initialize for 'lookup item'
for idp, phrase in enumerate(term.split()):
for k in keys:
if ('%s=' % k) in phrase:
thiskey = k
# update current key if used
if '=' in phrase:
for k in valid_keys:
if ('%s=' % k) in phrase:
thiskey = k
# if first term or key does not exist
if idp == 0 or not params[thiskey]:
params[thiskey] = phrase
keys.append(thiskey)
else:
# append to existing key
params[thiskey] += ' ' + phrase
rparams = [params[x] for x in keys if params[x]]
return rparams
# return list of values
return [params[x] for x in keys]
class LookupModule(LookupBase):
@ -108,36 +118,36 @@ class LookupModule(LookupBase):
def run(self, terms, variables=None, **kwargs):
self.set_options(var_options=variables, direct=kwargs)
paramvals = self.get_options()
self.cp = configparser.ConfigParser()
ret = []
for term in terms:
params = _parse_params(term)
key = params[0]
paramvals = {
'file': 'ansible.ini',
're': False,
'default': None,
'section': "global",
'type': "ini",
'encoding': 'utf-8',
}
key = term
# parameters specified?
try:
for param in params[1:]:
name, value = param.split('=')
if name not in paramvals:
raise AnsibleAssertionError('%s not in paramvals' %
name)
paramvals[name] = value
except (ValueError, AssertionError) as e:
raise AnsibleError(e)
if '=' in term or ' ' in term.strip():
self._deprecate_inline_kv()
params = _parse_params(term, paramvals)
try:
for param in params:
if '=' in param:
name, value = param.split('=')
if name not in paramvals:
raise AnsibleLookupError('%s is not a valid option.' % name)
paramvals[name] = value
elif key == term:
# only take first, this format never supported multiple keys inline
key = param
except ValueError as e:
# bad params passed
raise AnsibleLookupError("Could not use '%s' from '%s': %s" % (param, params, to_native(e)), orig_exc=e)
# TODO: look to use cache to avoid redoing this for every term if they use same file
# Retrieve file path
path = self.find_file_in_search_path(variables, 'files',
paramvals['file'])
path = self.find_file_in_search_path(variables, 'files', paramvals['file'])
# Create StringIO later used to parse ini
config = StringIO()
@ -148,14 +158,12 @@ class LookupModule(LookupBase):
# Open file using encoding
contents, show_data = self._loader._get_file_contents(path)
contents = to_text(contents, errors='surrogate_or_strict',
encoding=paramvals['encoding'])
contents = to_text(contents, errors='surrogate_or_strict', encoding=paramvals['encoding'])
config.write(contents)
config.seek(0, os.SEEK_SET)
self.cp.readfp(config)
var = self.get_value(key, paramvals['section'],
paramvals['default'], paramvals['re'])
var = self.get_value(key, paramvals['section'], paramvals['default'], paramvals['re'])
if var is not None:
if isinstance(var, MutableSequence):
for v in var:

View file

@ -40,6 +40,11 @@ DOCUMENTATION = """
default: False
version_added: '2.11'
type: bool
template_vars:
description: A dictionary, the keys become additional variables available for templating.
default: {}
version_added: '2.3'
type: dict
"""
EXAMPLES = """
@ -78,13 +83,17 @@ display = Display()
class LookupModule(LookupBase):
def run(self, terms, variables, **kwargs):
convert_data_p = kwargs.get('convert_data', True)
lookup_template_vars = kwargs.get('template_vars', {})
jinja2_native = kwargs.get('jinja2_native', False)
ret = []
variable_start_string = kwargs.get('variable_start_string', None)
variable_end_string = kwargs.get('variable_end_string', None)
self.set_options(var_options=variables, direct=kwargs)
# capture options
convert_data_p = self.get_option('convert_data')
lookup_template_vars = self.get_option('template_vars')
jinja2_native = self.get_option('jinja2_native')
variable_start_string = self.get_option('variable_start_string')
variable_end_string = self.get_option('variable_end_string')
if USE_JINJA2_NATIVE and not jinja2_native:
templar = self._templar.copy_with_new_env(environment_class=AnsibleEnvironment)

View file

@ -42,10 +42,10 @@ class LookupModule(LookupBase):
def run(self, terms, variables=None, **kwargs):
self.set_options(direct=kwargs)
ret = []
self.set_options(var_options=variables, direct=kwargs)
for term in terms:
display.debug("Unvault lookup term: %s" % term)

View file

@ -58,8 +58,7 @@ class LookupModule(LookupBase):
if variables is None:
raise AnsibleError('No variables available to search')
# no options, yet
# self.set_options(direct=kwargs)
self.set_options(var_options=variables, direct=kwargs)
ret = []
variable_names = list(variables.keys())

View file

@ -79,7 +79,7 @@ class LookupModule(LookupBase):
self._templar.available_variables = variables
myvars = getattr(self._templar, '_available_variables', {})
self.set_options(direct=kwargs)
self.set_options(var_options=variables, direct=kwargs)
default = self.get_option('default')
ret = []

View file

@ -42,7 +42,15 @@ from jinja2.loaders import FileSystemLoader
from jinja2.runtime import Context, StrictUndefined
from ansible import constants as C
from ansible.errors import AnsibleError, AnsibleFilterError, AnsiblePluginRemovedError, AnsibleUndefinedVariable, AnsibleAssertionError
from ansible.errors import (
AnsibleAssertionError,
AnsibleError,
AnsibleFilterError,
AnsibleLookupError,
AnsibleOptionsError,
AnsiblePluginRemovedError,
AnsibleUndefinedVariable,
)
from ansible.module_utils.six import iteritems, string_types, text_type
from ansible.module_utils.six.moves import range
from ansible.module_utils._text import to_native, to_text, to_bytes
@ -997,7 +1005,22 @@ class Templar:
ran = instance.run(loop_terms, variables=self._available_variables, **kwargs)
except (AnsibleUndefinedVariable, UndefinedError) as e:
raise AnsibleUndefinedVariable(e)
except AnsibleOptionsError as e:
# invalid options given to lookup, just reraise
raise e
except AnsibleLookupError as e:
# lookup handled error but still decided to bail
if self._fail_on_lookup_errors:
msg = 'Lookup failed but the error is being ignored: %s' % to_native(e)
if errors == 'warn':
display.warning(msg)
elif errors == 'ignore':
display.display(msg, log_only=True)
else:
raise e
return [] if wantlist else None
except Exception as e:
# errors not handled by lookup
if self._fail_on_lookup_errors:
msg = u"An unhandled exception occurred while running the lookup plugin '%s'. Error was a %s, original message: %s" % \
(name, type(e), to_text(e))
@ -1006,7 +1029,8 @@ class Templar:
elif errors == 'ignore':
display.display(msg, log_only=True)
else:
raise AnsibleError(to_native(msg))
display.vvv('exception during Jinja2 execution: {0}'.format(format_exc()))
raise AnsibleError(to_native(msg), orig_exc=e)
return [] if wantlist else None
if ran and allow_unsafe is False:

View file

@ -1,15 +1,23 @@
- set_fact:
this_will_error: "{{ lookup('csvfile', 'file=people.csv delimiter=, col=1') }}"
- name: using deprecated syntax but missing keyword
set_fact:
this_will_error: "{{ lookup('csvfile', 'file=people.csv, delimiter=, col=1') }}"
ignore_errors: yes
register: no_keyword
- set_fact:
- name: extra arg in k=v syntax (deprecated)
set_fact:
this_will_error: "{{ lookup('csvfile', 'foo file=people.csv delimiter=, col=1 thisarg=doesnotexist') }}"
ignore_errors: yes
register: invalid_arg
- name: extra arg in config syntax
set_fact:
this_will_error: "{{ lookup('csvfile', 'foo', file='people.csv', delimiter=',' col=1, thisarg='doesnotexist') }}"
ignore_errors: yes
register: invalid_arg2
- set_fact:
this_will_error: "{{ lookup('csvfile', 'foo file=doesnotexist delimiter=, col=1') }}"
this_will_error: "{{ lookup('csvfile', 'foo', file='doesnotexist', delimiter=',', col=1) }}"
ignore_errors: yes
register: missing_file
@ -19,24 +27,30 @@
- no_keyword is failed
- >
"Search key is required but was not found" in no_keyword.msg
- invalid_arg is failed
- invalid_arg2 is failed
- >
"not in paramvals" in invalid_arg.msg
"is not a valid option" in invalid_arg.msg
- missing_file is failed
- >
"need string or buffer" in missing_file.msg or "expected str, bytes or os.PathLike object" in missing_file.msg
"need string or buffer" in missing_file.msg or
"expected str, bytes or os.PathLike object" in missing_file.msg or
"No such file or directory" in missing_file.msg
- name: Check basic comma-separated file
assert:
that:
- lookup('csvfile', 'Smith file=people.csv delimiter=, col=1') == "Jane"
- lookup('csvfile', 'Smith', file='people.csv', delimiter=',', col=1) == "Jane"
- lookup('csvfile', 'German von Lastname file=people.csv delimiter=, col=1') == "Demo"
- name: Check tab-separated file
assert:
that:
- lookup('csvfile', 'electronics file=tabs.csv delimiter=TAB col=1') == "tvs"
- lookup('csvfile', 'fruit file=tabs.csv delimiter=TAB col=1') == "bananas"
- "lookup('csvfile', 'fruit', file='tabs.csv', delimiter='TAB', col=1) == 'bananas'"
- lookup('csvfile', 'fruit file=tabs.csv delimiter="\t" col=1') == "bananas"
- lookup('csvfile', 'electronics', 'fruit', file='tabs.csv', delimiter='\t', col=1) == "tvs,bananas"
- lookup('csvfile', 'electronics', 'fruit', file='tabs.csv', delimiter='\t', col=1, wantlist=True) == ["tvs", "bananas"]
- name: Check \x1a-separated file
assert:

View file

@ -1,10 +1,9 @@
- name: test with_first_found
#shell: echo {{ item }}
set_fact: "first_found={{ item }}"
with_first_found:
- "{{ role_path + '/files/does_not_exist' }}"
- "{{ role_path + '/files/foo1' }}"
- "{{ role_path + '/files/bar1' }}"
- "does_not_exist"
- "foo1"
- "{{ role_path + '/files/bar1' }}" # will only hit this if dwim search is broken
- name: set expected
set_fact: first_expected="{{ role_path + '/files/foo1' }}"
@ -24,6 +23,7 @@
vars:
params:
files: "not_a_file.yaml"
skip: True
- name: verify q(first_found) result
assert:
@ -71,3 +71,16 @@
assert:
that:
- "this_not_set is not defined"
- name: test legacy formats
set_fact: hatethisformat={{item}}
vars:
params:
files: not/a/file.yaml;hosts
paths: not/a/path:/etc
loop: "{{ q('first_found', params) }}"
- name: verify /etc/hosts was found
assert:
that:
- "hatethisformat == '/etc/hosts'"

View file

@ -6,18 +6,18 @@
- name: "read properties value"
set_fact:
test1: "{{lookup('ini', 'value1 type=properties file=lookup.properties')}}"
test2: "{{lookup('ini', 'value2 type=properties file=lookup.properties')}}"
test_dot: "{{lookup('ini', 'value.dot type=properties file=lookup.properties')}}"
test2: "{{lookup('ini', 'value2', type='properties', file='lookup.properties')}}"
test_dot: "{{lookup('ini', 'value.dot', type='properties', file='lookup.properties')}}"
field_with_space: "{{lookup('ini', 'field.with.space type=properties file=lookup.properties')}}"
- assert:
that: "{{item}} is defined"
with_items: [ 'test1', 'test2', 'test_dot', 'field_with_space' ]
- name: "read ini value"
set_fact:
value1_global: "{{lookup('ini', 'value1 section=global file=lookup.ini')}}"
value2_global: "{{lookup('ini', 'value2 section=global file=lookup.ini')}}"
value1_section1: "{{lookup('ini', 'value1 section=section1 file=lookup.ini')}}"
field_with_unicode: "{{lookup('ini', 'unicode section=global file=lookup.ini')}}"
value1_global: "{{lookup('ini', 'value1', section='global', file='lookup.ini')}}"
value2_global: "{{lookup('ini', 'value2', section='global', file='lookup.ini')}}"
value1_section1: "{{lookup('ini', 'value1', section='section1', file='lookup.ini')}}"
field_with_unicode: "{{lookup('ini', 'unicode', section='global', file='lookup.ini')}}"
- debug: var={{item}}
with_items: [ 'value1_global', 'value2_global', 'value1_section1', 'field_with_unicode' ]
- assert:

View file

@ -56,8 +56,9 @@ class TestINILookup(unittest.TestCase):
)
def test_parse_parameters(self):
pvals = {'file': '', 'section': '', 'key': '', 'type': '', 're': '', 'default': '', 'encoding': ''}
for testcase in self.old_style_params_data:
# print(testcase)
params = _parse_params(testcase['term'])
params = _parse_params(testcase['term'], pvals)
params.sort()
self.assertEqual(params, testcase['expected'])