1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220
|
# (c) 2015, Yannig Perre <yannig.perre(at)gmail.com>
# (c) 2017 Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import annotations
DOCUMENTATION = """
name: ini
author: Yannig Perre (!UNKNOWN) <yannig.perre(at)gmail.com>
version_added: "2.0"
short_description: read data from an ini file
description:
- "The ini lookup reads the contents of a file in INI format C(key1=value1).
This plugin retrieves the value on the right side after the equal sign C('=') of a given section C([section])."
- "You can also read a property file which - in this case - does not contain section."
options:
_terms:
description: The key(s) to look up.
required: True
type:
description: Type of the file. 'properties' refers to the Java properties files.
default: 'ini'
choices: ['ini', 'properties']
file:
description: Name of the file to load.
default: 'ansible.ini'
section:
default: global
description: Section where to lookup the key.
re:
default: False
type: boolean
description: Flag to indicate if the key supplied is a regexp.
encoding:
default: utf-8
description: Text encoding to use.
default:
description: Return value if the key is not in the ini file.
default: ''
case_sensitive:
description:
Whether key names read from O(file) should be case sensitive. This prevents
duplicate key errors if keys only differ in case.
default: False
version_added: '2.12'
allow_no_value:
description:
- Read an ini file which contains key without value and without '=' symbol.
type: bool
default: False
aliases: ['allow_none']
version_added: '2.12'
interpolation:
description:
Allows for interpolation of values, see https://docs.python.org/3/library/configparser.html#configparser.BasicInterpolation
type: bool
default: True
version_added: '2.18'
seealso:
- ref: playbook_task_paths
description: Search paths used for relative files.
"""
EXAMPLES = """
- ansible.builtin.debug: msg="User in integration is {{ lookup('ansible.builtin.ini', 'user', section='integration', file='users.ini') }}"
- ansible.builtin.debug: msg="User in production is {{ lookup('ansible.builtin.ini', 'user', section='production', file='users.ini') }}"
- ansible.builtin.debug: msg="user.name is {{ lookup('ansible.builtin.ini', 'user.name', type='properties', file='user.properties') }}"
- ansible.builtin.debug:
msg: "{{ item }}"
loop: "{{ q('ansible.builtin.ini', '.*', section='section1', file='test.ini', re=True) }}"
- name: Read an ini file with allow_no_value
ansible.builtin.debug:
msg: "{{ lookup('ansible.builtin.ini', 'user', file='mysql.ini', section='mysqld', allow_no_value=True) }}"
"""
RETURN = """
_raw:
description:
- value(s) of the key(s) in the ini file
type: list
elements: str
"""
import configparser
import os
import re
from io import StringIO
from collections import defaultdict
from collections.abc import MutableSequence
from ansible.errors import AnsibleError
from ansible.module_utils.common.text.converters import to_native
from ansible.plugins.lookup import LookupBase
def _parse_params(term, paramvals):
"""Safely split parameter term to preserve spaces"""
# TODO: deprecate this method
valid_keys = paramvals.keys()
params = defaultdict(lambda: '')
# 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()):
# 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
# return list of values
return [params[x] for x in keys]
class LookupModule(LookupBase):
def get_value(self, key, section, dflt, is_regexp):
# Retrieve all values from a section using a regexp
if is_regexp:
return [v for k, v in self.cp.items(section) if re.match(key, k)]
value = None
# Retrieve a single value
try:
value = self.cp.get(section, key)
except configparser.NoOptionError:
return dflt
return value
def run(self, terms, variables=None, **kwargs):
self.set_options(var_options=variables, direct=kwargs)
paramvals = self.get_options()
self.cp = configparser.ConfigParser(
allow_no_value=paramvals.get('allow_no_value', paramvals.get('allow_none')),
interpolation=configparser.BasicInterpolation() if paramvals.get('interpolation') else None,
)
if paramvals['case_sensitive']:
self.cp.optionxform = to_native
ret = []
for term in terms:
key = term
# parameters specified?
if '=' in term or ' ' in term.strip():
self._deprecate_inline_kv()
params = _parse_params(term, paramvals)
param = None
try:
updated_key = False
updated_options = False
for param in params:
if '=' in param:
name, value = param.split('=')
if name not in paramvals:
raise AnsibleError(f"{name!r} is not a valid option.")
self.set_option(name, value)
updated_options = True
elif key == term:
# only take first, this format never supported multiple keys inline
key = param
updated_key = True
if updated_options:
paramvals = self.get_options()
except ValueError as ex:
# bad params passed
raise ValueError(f"Could not use {param!r} from {params!r}.") from ex
if not updated_key:
raise ValueError(f"No key to look up was provided as first term within string inline options: {term}")
# only passed options in inline string
# 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'])
# Create StringIO later used to parse ini
config = StringIO()
# Special case for java properties
if paramvals['type'] == "properties":
config.write(u'[java_properties]\n')
paramvals['section'] = 'java_properties'
contents = self._loader.get_text_file_contents(path, encoding=paramvals['encoding'])
config.write(contents)
config.seek(0, os.SEEK_SET)
try:
self.cp.read_file(config)
except configparser.DuplicateOptionError as ex:
raise ValueError(f"Duplicate option in {paramvals['file']!r}.") from ex
try:
var = self.get_value(key, paramvals['section'], paramvals['default'], paramvals['re'])
except configparser.NoSectionError:
raise ValueError(f"No section {paramvals['section']!r} in {paramvals['file']!r}.") from None
if var is not None:
if isinstance(var, MutableSequence):
for v in var:
ret.append(v)
else:
ret.append(var)
return ret
|