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 <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"""
60import os
61import re
62from io import StringIO
63
64from ansible.errors import AnsibleError, AnsibleAssertionError
65from ansible.module_utils.six.moves import configparser
66from ansible.module_utils._text import to_bytes, to_text
67from ansible.module_utils.common._collections_compat import MutableSequence
68from ansible.plugins.lookup import LookupBase
69
70
71def _parse_params(term):
72    '''Safely split parameter term to preserve spaces'''
73
74    keys = ['key', 'type', 'section', 'file', 're', 'default', 'encoding']
75    params = {}
76    for k in keys:
77        params[k] = ''
78
79    thiskey = 'key'
80    for idp, phrase in enumerate(term.split()):
81        for k in keys:
82            if ('%s=' % k) in phrase:
83                thiskey = k
84        if idp == 0 or not params[thiskey]:
85            params[thiskey] = phrase
86        else:
87            params[thiskey] += ' ' + phrase
88
89    rparams = [params[x] for x in keys if params[x]]
90    return rparams
91
92
93class LookupModule(LookupBase):
94
95    def get_value(self, key, section, dflt, is_regexp):
96        # Retrieve all values from a section using a regexp
97        if is_regexp:
98            return [v for k, v in self.cp.items(section) if re.match(key, k)]
99        value = None
100        # Retrieve a single value
101        try:
102            value = self.cp.get(section, key)
103        except configparser.NoOptionError:
104            return dflt
105        return value
106
107    def run(self, terms, variables=None, **kwargs):
108
109        self.cp = configparser.ConfigParser()
110
111        ret = []
112        for term in terms:
113            params = _parse_params(term)
114            key = params[0]
115
116            paramvals = {
117                'file': 'ansible.ini',
118                're': False,
119                'default': None,
120                'section': "global",
121                'type': "ini",
122                'encoding': 'utf-8',
123            }
124
125            # parameters specified?
126            try:
127                for param in params[1:]:
128                    name, value = param.split('=')
129                    if name not in paramvals:
130                        raise AnsibleAssertionError('%s not in paramvals' %
131                                                    name)
132                    paramvals[name] = value
133            except (ValueError, AssertionError) as e:
134                raise AnsibleError(e)
135
136            # Retrieve file path
137            path = self.find_file_in_search_path(variables, 'files',
138                                                 paramvals['file'])
139
140            # Create StringIO later used to parse ini
141            config = StringIO()
142            # Special case for java properties
143            if paramvals['type'] == "properties":
144                config.write(u'[java_properties]\n')
145                paramvals['section'] = 'java_properties'
146
147            # Open file using encoding
148            contents, show_data = self._loader._get_file_contents(path)
149            contents = to_text(contents, errors='surrogate_or_strict',
150                               encoding=paramvals['encoding'])
151            config.write(contents)
152            config.seek(0, os.SEEK_SET)
153
154            self.cp.readfp(config)
155            var = self.get_value(key, paramvals['section'],
156                                 paramvals['default'], paramvals['re'])
157            if var is not None:
158                if isinstance(var, MutableSequence):
159                    for v in var:
160                        ret.append(v)
161                else:
162                    ret.append(var)
163        return ret
164