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
|
# (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 (absolute_import, division, print_function)
__metaclass__ = type
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 C(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'
"""
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 ansible.errors import AnsibleLookupError, AnsibleOptionsError
from ansible.module_utils._text import to_text, to_native
from ansible.module_utils.common._collections_compat import MutableSequence
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')))
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)
try:
updated_key = False
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
updated_key = True
except ValueError as e:
# bad params passed
raise AnsibleLookupError("Could not use '%s' from '%s': %s" % (param, params, to_native(e)), orig_exc=e)
if not updated_key:
raise AnsibleOptionsError("No key to lookup was provided as first term with in string inline options: %s" % 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'
# Open file using encoding
contents, show_data = self._loader._get_file_contents(path)
contents = to_text(contents, errors='surrogate_or_strict', encoding=paramvals['encoding'])
config.write(contents)
config.seek(0, os.SEEK_SET)
try:
self.cp.readfp(config)
except configparser.DuplicateOptionError as doe:
raise AnsibleLookupError("Duplicate option in '{file}': {error}".format(file=paramvals['file'], error=to_native(doe)))
try:
var = self.get_value(key, paramvals['section'], paramvals['default'], paramvals['re'])
except configparser.NoSectionError:
raise AnsibleLookupError("No section '{section}' in {file}".format(section=paramvals['section'], file=paramvals['file']))
if var is not None:
if isinstance(var, MutableSequence):
for v in var:
ret.append(v)
else:
ret.append(var)
return ret
|