1# (c) 2015, Yannig Perre <yannig.perre(at)gmail.com>
2# (c) 2017 Ansible Project
3# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
4from __future__ import (absolute_import, division, print_function)
5__metaclass__ = type
6
7DOCUMENTATION = """
8    name: ini
9    author: Yannig Perre (!UNKNOWN) <yannig.perre(at)gmail.com>
10    version_added: "2.0"
11    short_description: read data from a ini file
12    description:
13      - "The ini lookup reads the contents of a file in INI format C(key1=value1).
14        This plugin retrieves the value on the right side after the equal sign C('=') of a given section C([section])."
15      - "You can also read a property file which - in this case - does not contain section."
16    options:
17      _terms:
18        description:
19            The key(s) to look up. On Python 2, key names are case B(insensitive).
20            In Python 3, key names are case B(sensitive). Duplicate key names found
21            in a file will result in an error.
22        required: True
23      type:
24        description: Type of the file. 'properties' refers to the Java properties files.
25        default: 'ini'
26        choices: ['ini', 'properties']
27      file:
28        description: Name of the file to load.
29        default: ansible.ini
30      section:
31        default: global
32        description: Section where to lookup the key.
33      re:
34        default: False
35        type: boolean
36        description: Flag to indicate if the key supplied is a regexp.
37      encoding:
38        default: utf-8
39        description:  Text encoding to use.
40      default:
41        description: Return value if the key is not in the ini file.
42        default: ''
43"""
44
45EXAMPLES = """
46- debug: msg="User in integration is {{ lookup('ini', 'user section=integration file=users.ini') }}"
47
48- debug: msg="User in production  is {{ lookup('ini', 'user section=production  file=users.ini') }}"
49
50- debug: msg="user.name is {{ lookup('ini', 'user.name type=properties file=user.properties') }}"
51
52- debug:
53    msg: "{{ item }}"
54  with_ini:
55    - '.* section=section1 file=test.ini re=True'
56"""
57
58RETURN = """
59_raw:
60  description:
61    - value(s) of the key(s) in the ini file
62  type: list
63  elements: str
64"""
65import os
66import re
67from io import StringIO
68
69from ansible.errors import AnsibleError, AnsibleAssertionError, AnsibleLookupError
70from ansible.module_utils.six.moves import configparser
71from ansible.module_utils._text import to_text, to_native
72from ansible.module_utils.common._collections_compat import MutableSequence
73from ansible.plugins.lookup import LookupBase
74
75
76def _parse_params(term):
77    '''Safely split parameter term to preserve spaces'''
78
79    keys = ['key', 'type', 'section', 'file', 're', 'default', 'encoding']
80    params = {}
81    for k in keys:
82        params[k] = ''
83
84    thiskey = 'key'
85    for idp, phrase in enumerate(term.split()):
86        for k in keys:
87            if ('%s=' % k) in phrase:
88                thiskey = k
89        if idp == 0 or not params[thiskey]:
90            params[thiskey] = phrase
91        else:
92            params[thiskey] += ' ' + phrase
93
94    rparams = [params[x] for x in keys if params[x]]
95    return rparams
96
97
98class LookupModule(LookupBase):
99
100    def get_value(self, key, section, dflt, is_regexp):
101        # Retrieve all values from a section using a regexp
102        if is_regexp:
103            return [v for k, v in self.cp.items(section) if re.match(key, k)]
104        value = None
105        # Retrieve a single value
106        try:
107            value = self.cp.get(section, key)
108        except configparser.NoOptionError:
109            return dflt
110        return value
111
112    def run(self, terms, variables=None, **kwargs):
113
114        self.cp = configparser.ConfigParser()
115
116        ret = []
117        for term in terms:
118            params = _parse_params(term)
119            key = params[0]
120
121            paramvals = {
122                'file': 'ansible.ini',
123                're': False,
124                'default': None,
125                'section': "global",
126                'type': "ini",
127                'encoding': 'utf-8',
128            }
129
130            # parameters specified?
131            try:
132                for param in params[1:]:
133                    name, value = param.split('=')
134                    if name not in paramvals:
135                        raise AnsibleAssertionError('%s not in paramvals' %
136                                                    name)
137                    paramvals[name] = value
138            except (ValueError, AssertionError) as e:
139                raise AnsibleError(e)
140
141            # Retrieve file path
142            path = self.find_file_in_search_path(variables, 'files',
143                                                 paramvals['file'])
144
145            # Create StringIO later used to parse ini
146            config = StringIO()
147            # Special case for java properties
148            if paramvals['type'] == "properties":
149                config.write(u'[java_properties]\n')
150                paramvals['section'] = 'java_properties'
151
152            # Open file using encoding
153            contents, show_data = self._loader._get_file_contents(path)
154            contents = to_text(contents, errors='surrogate_or_strict',
155                               encoding=paramvals['encoding'])
156            config.write(contents)
157            config.seek(0, os.SEEK_SET)
158
159            try:
160                self.cp.readfp(config)
161            except configparser.DuplicateOptionError as doe:
162                raise AnsibleLookupError("Duplicate option in '{file}': {error}".format(file=paramvals['file'], error=to_native(doe)))
163
164            try:
165                var = self.get_value(key, paramvals['section'], paramvals['default'], paramvals['re'])
166            except configparser.NoSectionError:
167                raise AnsibleLookupError("No section '{section}' in {file}".format(section=paramvals['section'], file=paramvals['file']))
168            if var is not None:
169                if isinstance(var, MutableSequence):
170                    for v in var:
171                        ret.append(v)
172                else:
173                    ret.append(var)
174        return ret
175