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    lookup: 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: The key(s) to look up
19        required: True
20      type:
21        description: Type of the file. 'properties' refers to the Java properties files.
22        default: 'ini'
23        choices: ['ini', 'properties']
24      file:
25        description: Name of the file to load.
26        default: ansible.ini
27      section:
28        default: global
29        description: Section where to lookup the key.
30      re:
31        default: False
32        type: boolean
33        description: Flag to indicate if the key supplied is a regexp.
34      encoding:
35        default: utf-8
36        description:  Text encoding to use.
37      default:
38        description: Return value if the key is not in the ini file.
39        default: ''
40"""
41
42EXAMPLES = """
43- debug: msg="User in integration is {{ lookup('ini', 'user section=integration file=users.ini') }}"
44
45- debug: msg="User in production  is {{ lookup('ini', 'user section=production  file=users.ini') }}"
46
47- debug: msg="user.name is {{ lookup('ini', 'user.name type=properties file=user.properties') }}"
48
49- debug:
50    msg: "{{ item }}"
51  with_ini:
52    - '.* section=section1 file=test.ini re=True'
53"""
54
55RETURN = """
56_raw:
57  description:
58    - value(s) of the key(s) in the ini file
59  type: list
60  elements: str
61"""
62import os
63import re
64from io import StringIO
65
66from ansible.errors import AnsibleError, AnsibleAssertionError
67from ansible.module_utils.six.moves import configparser
68from ansible.module_utils._text import to_bytes, to_text
69from ansible.module_utils.common._collections_compat import MutableSequence
70from ansible.plugins.lookup import LookupBase
71
72
73def _parse_params(term):
74    '''Safely split parameter term to preserve spaces'''
75
76    keys = ['key', 'type', 'section', 'file', 're', 'default', 'encoding']
77    params = {}
78    for k in keys:
79        params[k] = ''
80
81    thiskey = 'key'
82    for idp, phrase in enumerate(term.split()):
83        for k in keys:
84            if ('%s=' % k) in phrase:
85                thiskey = k
86        if idp == 0 or not params[thiskey]:
87            params[thiskey] = phrase
88        else:
89            params[thiskey] += ' ' + phrase
90
91    rparams = [params[x] for x in keys if params[x]]
92    return rparams
93
94
95class LookupModule(LookupBase):
96
97    def get_value(self, key, section, dflt, is_regexp):
98        # Retrieve all values from a section using a regexp
99        if is_regexp:
100            return [v for k, v in self.cp.items(section) if re.match(key, k)]
101        value = None
102        # Retrieve a single value
103        try:
104            value = self.cp.get(section, key)
105        except configparser.NoOptionError:
106            return dflt
107        return value
108
109    def run(self, terms, variables=None, **kwargs):
110
111        self.cp = configparser.ConfigParser()
112
113        ret = []
114        for term in terms:
115            params = _parse_params(term)
116            key = params[0]
117
118            paramvals = {
119                'file': 'ansible.ini',
120                're': False,
121                'default': None,
122                'section': "global",
123                'type': "ini",
124                'encoding': 'utf-8',
125            }
126
127            # parameters specified?
128            try:
129                for param in params[1:]:
130                    name, value = param.split('=')
131                    if name not in paramvals:
132                        raise AnsibleAssertionError('%s not in paramvals' %
133                                                    name)
134                    paramvals[name] = value
135            except (ValueError, AssertionError) as e:
136                raise AnsibleError(e)
137
138            # Retrieve file path
139            path = self.find_file_in_search_path(variables, 'files',
140                                                 paramvals['file'])
141
142            # Create StringIO later used to parse ini
143            config = StringIO()
144            # Special case for java properties
145            if paramvals['type'] == "properties":
146                config.write(u'[java_properties]\n')
147                paramvals['section'] = 'java_properties'
148
149            # Open file using encoding
150            contents, show_data = self._loader._get_file_contents(path)
151            contents = to_text(contents, errors='surrogate_or_strict',
152                               encoding=paramvals['encoding'])
153            config.write(contents)
154            config.seek(0, os.SEEK_SET)
155
156            self.cp.readfp(config)
157            var = self.get_value(key, paramvals['section'],
158                                 paramvals['default'], paramvals['re'])
159            if var is not None:
160                if isinstance(var, MutableSequence):
161                    for v in var:
162                        ret.append(v)
163                else:
164                    ret.append(var)
165        return ret
166