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