From c2968d6d84fb5aa66ae50d9df0bed963f22abbd8 Mon Sep 17 00:00:00 2001 From: Yannig Perre Date: Wed, 27 May 2015 21:51:20 +0200 Subject: [PATCH 1/5] New lookup plugin : ini. Can handle ini file and java properties file. Can also read a list of value in a section using regexp. --- lib/ansible/runner/lookup_plugins/ini.py | 92 +++++++++++++++++++++ test/integration/lookup.ini | 24 ++++++ test/integration/lookup.properties | 5 ++ test/integration/test_lookup_properties.yml | 29 +++++++ 4 files changed, 150 insertions(+) create mode 100644 lib/ansible/runner/lookup_plugins/ini.py create mode 100644 test/integration/lookup.ini create mode 100644 test/integration/lookup.properties create mode 100644 test/integration/test_lookup_properties.yml diff --git a/lib/ansible/runner/lookup_plugins/ini.py b/lib/ansible/runner/lookup_plugins/ini.py new file mode 100644 index 00000000000..002dda09089 --- /dev/null +++ b/lib/ansible/runner/lookup_plugins/ini.py @@ -0,0 +1,92 @@ +# (c) 2015, Yannig Perre +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . + +from ansible import utils, errors +import StringIO +import os +import codecs +import ConfigParser +import re + +class LookupModule(object): + + def __init__(self, basedir=None, **kwargs): + self.basedir = basedir + self.cp = ConfigParser.ConfigParser() + + def read_properties(self, filename, key, dflt, is_regexp): + config = StringIO.StringIO() + config.write('[java_properties]\n' + open(filename).read()) + config.seek(0, os.SEEK_SET) + self.cp.readfp(config) + return self.get_value(key, 'java_properties', dflt, is_regexp) + + def read_ini(self, filename, key, section, dflt, is_regexp): + self.cp.readfp(open(filename)) + return self.get_value(key, section, dflt, is_regexp) + + 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)] + # Retrieve a single value + value = self.cp.get(section, key) + if value == None: + return dflt + return value + + def run(self, terms, inject=None, **kwargs): + + terms = utils.listify_lookup_plugin_terms(terms, self.basedir, inject) + + if isinstance(terms, basestring): + terms = [ terms ] + + ret = [] + for term in terms: + params = term.split() + key = params[0] + + paramvals = { + 'file' : 'ansible.ini', + 're' : False, + 'default' : None, + 'section' : "global", + 'type' : "ini", + } + + # parameters specified? + try: + for param in params[1:]: + name, value = param.split('=') + assert(name in paramvals) + paramvals[name] = value + except (ValueError, AssertionError), e: + raise errors.AnsibleError(e) + + path = utils.path_dwim(self.basedir, paramvals['file']) + if paramvals['type'] == "properties": + var = self.read_properties(path, key, paramvals['default'], paramvals['re']) + else: + var = self.read_ini(path, key, paramvals['section'], paramvals['default'], paramvals['re']) + if var is not None: + if type(var) is list: + for v in var: + ret.append(v) + else: + ret.append(var) + return ret diff --git a/test/integration/lookup.ini b/test/integration/lookup.ini new file mode 100644 index 00000000000..ce0dbf84860 --- /dev/null +++ b/test/integration/lookup.ini @@ -0,0 +1,24 @@ +[global] +# A comment +value1=Text associated with value1 and global section +value2=Same for value2 and global section +value.dot=Properties with dot +field.with.space = another space + +[section1] +value1=Another value for section1 +# No value2 in this section + +[value_section] +value1=1 +value2=2 +value3=3 +other1=4 +other2=5 + +[other_section] +value1=1 +value2=2 +value3=3 +other1=4 +other2=5 diff --git a/test/integration/lookup.properties b/test/integration/lookup.properties new file mode 100644 index 00000000000..f388d8cfbf5 --- /dev/null +++ b/test/integration/lookup.properties @@ -0,0 +1,5 @@ +# A comment +value1=Text associated with value1 +value2=Same for value2 +value.dot=Properties with dot +field.with.space = another space diff --git a/test/integration/test_lookup_properties.yml b/test/integration/test_lookup_properties.yml new file mode 100644 index 00000000000..dcd5eb698b2 --- /dev/null +++ b/test/integration/test_lookup_properties.yml @@ -0,0 +1,29 @@ +--- +- name: "Lookup test" + hosts: "localhost" +# connection: local + tasks: + - name: "read properties value" + set_fact: + test1: "{{lookup('ini', 'value1 type=properties file=lookup.properties')}}" + test2: "{{lookup('ini', 'value2 type=properties file=lookup.properties')}}" + test_dot: "{{lookup('ini', 'value.dot type=properties file=lookup.properties')}}" + field_with_space: "{{lookup('ini', 'field.with.space type=properties file=lookup.properties')}}" + - debug: var={{item}} + with_items: [ 'test1', 'test2', 'test_dot', 'field_with_space' ] + - name: "read ini value" + set_fact: + value1_global: "{{lookup('ini', 'value1 section=global file=lookup.ini')}}" + value2_global: "{{lookup('ini', 'value2 section=global file=lookup.ini')}}" + value1_section1: "{{lookup('ini', 'value1 section=section1 file=lookup.ini')}}" + - debug: var={{item}} + with_items: [ 'value1_global', 'value2_global', 'value1_section1' ] + - name: "read ini value with section and regexp" + set_fact: + value_section: "{{lookup('ini', 'value[1-2] section=value_section file=lookup.ini re=true')}}" + other_section: "{{lookup('ini', 'other[1-2] section=other_section file=lookup.ini re=true')}}" + - debug: var={{item}} + with_items: [ 'value_section', 'other_section' ] + - name: "Reading unknown value" + set_fact: + value2_section2: "{{lookup('ini', 'value2 section=section1 file=lookup.ini')}}" From 733d40a77c34998bf3bad3969bcd335b21baba44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yannig=20Perr=C3=A9?= Date: Wed, 5 Aug 2015 10:49:41 +0200 Subject: [PATCH 2/5] When value does not exist, return default value instead of stopping ansible with an exception. --- lib/ansible/runner/lookup_plugins/ini.py | 6 ++++-- test/integration/test_lookup_properties.yml | 3 ++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/lib/ansible/runner/lookup_plugins/ini.py b/lib/ansible/runner/lookup_plugins/ini.py index 002dda09089..ebfed750c2d 100644 --- a/lib/ansible/runner/lookup_plugins/ini.py +++ b/lib/ansible/runner/lookup_plugins/ini.py @@ -43,9 +43,11 @@ class LookupModule(object): # 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 - value = self.cp.get(section, key) - if value == None: + try: + value = self.cp.get(section, key) + except ConfigParser.NoOptionError, e: return dflt return value diff --git a/test/integration/test_lookup_properties.yml b/test/integration/test_lookup_properties.yml index dcd5eb698b2..4e085c14978 100644 --- a/test/integration/test_lookup_properties.yml +++ b/test/integration/test_lookup_properties.yml @@ -26,4 +26,5 @@ with_items: [ 'value_section', 'other_section' ] - name: "Reading unknown value" set_fact: - value2_section2: "{{lookup('ini', 'value2 section=section1 file=lookup.ini')}}" + unknown: "{{lookup('ini', 'value2 default=unknown section=section1 file=lookup.ini')}}" + - debug: var=unknown From c0bd14095914287e0ad7ab73ad6a9927e0c5a7f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yannig=20Perr=C3=A9?= Date: Wed, 5 Aug 2015 11:09:40 +0200 Subject: [PATCH 3/5] Add documentation for the ini lookup plugin. --- docsite/rst/playbooks_lookups.rst | 59 +++++++++++++++++++++ docsite/rst/playbooks_loops.rst | 48 +++++++++++++++++ test/integration/lookup.ini | 4 +- test/integration/test_lookup_properties.yml | 10 ++++ 4 files changed, 119 insertions(+), 2 deletions(-) diff --git a/docsite/rst/playbooks_lookups.rst b/docsite/rst/playbooks_lookups.rst index ac770dab39b..9659e86ad80 100644 --- a/docsite/rst/playbooks_lookups.rst +++ b/docsite/rst/playbooks_lookups.rst @@ -139,6 +139,65 @@ default empty string return value if the key is not in the csv file .. note:: The default delimiter is TAB, *not* comma. +.. _ini_lookup: + +The INI File Lookup +``````````````````` +.. versionadded:: 2.0 + +The ``ini`` lookup reads the contents of a file in INI format (key1=value1). +This plugin retrieve the value on the right side after the equal sign ('=') of +a given section ([section]). You can also read a property file which - in this +case - does not contain section. + +Here's a simple example of an INI file with user/password configuration:: + + [production] + # My production information + user=robert + pass=somerandompassword + + [integration] + # My integration information + user=gertrude + pass=anotherpassword + + +We can use the ``ini`` plugin to lookup user configuration:: + + - debug: msg="User in integration is {{ lookup('ini', 'user section=integration file=users.ini') }}" + - debug: msg="User in production is {{ lookup('ini', 'user section=production file=users.ini') }}" + +Another example for this plugin is for looking for a value on java properties. +Here's a simple properties we'll take as an example:: + + user.name=robert + user.pass=somerandompassword + +You can retrieve the ``user.name`` field with the following lookup:: + + - debug: msg="user.name is {{ lookup('ini', 'user.name type=property file=user.properties') }}" + +The ``ini`` lookup supports several arguments like the csv plugin. The format for passing +arguments is:: + + lookup('ini', 'key [type=] [section=section] [file=file.ini] [re=true] [default=]') + +The first value in the argument is the ``key``, which must be an entry that +appears exactly once on keys. All other arguments are optional. + + +========== ============ ========================================================================================= +Field Default Description +---------- ------------ ----------------------------------------------------------------------------------------- +type ini Type of the file. Can be ini or properties (for java properties). +file ansible.ini Name of the file to load +section global Default section where to lookup for key. +re False The key is a regexp. +default empty string return value if the key is not in the ini file +========== ============ ========================================================================================= + +.. note:: In java properties files, there's no need to specify a section. .. _more_lookups: diff --git a/docsite/rst/playbooks_loops.rst b/docsite/rst/playbooks_loops.rst index e71c81cefc2..56e43dd7d36 100644 --- a/docsite/rst/playbooks_loops.rst +++ b/docsite/rst/playbooks_loops.rst @@ -316,6 +316,54 @@ It's uncommonly used:: debug: msg="at array position {{ item.0 }} there is a value {{ item.1 }}" with_indexed_items: some_list +.. _using_ini_with_a_loop: + +Using ini file with a loop +`````````````````````````` +.. versionadded: 2.0 + +The ini plugin can use regexp to retrieve a set of keys. As a consequence, we can loop over this set. Here is the ini file we'll use:: + + [section1] + value1=section1/value1 + value2=section1/value2 + + [section2] + value1=section2/value1 + value2=section2/value2 + +Here is an example of using ``with_ini``:: + + - debug: msg="{{item}}" + with_ini: value[1-2] section=section1 file=lookup.ini re=true + +And here is the returned value:: + + { + "changed": false, + "msg": "All items completed", + "results": [ + { + "invocation": { + "module_args": "msg=\"section1/value1\"", + "module_name": "debug" + }, + "item": "section1/value1", + "msg": "section1/value1", + "verbose_always": true + }, + { + "invocation": { + "module_args": "msg=\"section1/value2\"", + "module_name": "debug" + }, + "item": "section1/value2", + "msg": "section1/value2", + "verbose_always": true + } + ] + } + .. _flattening_a_list: Flattening A List diff --git a/test/integration/lookup.ini b/test/integration/lookup.ini index ce0dbf84860..16500fd8990 100644 --- a/test/integration/lookup.ini +++ b/test/integration/lookup.ini @@ -6,8 +6,8 @@ value.dot=Properties with dot field.with.space = another space [section1] -value1=Another value for section1 -# No value2 in this section +value1=section1/value1 +value2=section1/value2 [value_section] value1=1 diff --git a/test/integration/test_lookup_properties.yml b/test/integration/test_lookup_properties.yml index 4e085c14978..4d22ce642c4 100644 --- a/test/integration/test_lookup_properties.yml +++ b/test/integration/test_lookup_properties.yml @@ -28,3 +28,13 @@ set_fact: unknown: "{{lookup('ini', 'value2 default=unknown section=section1 file=lookup.ini')}}" - debug: var=unknown + - name: "Looping over section section1" + debug: msg="{{item}}" + with_ini: value[1-2] section=section1 file=lookup.ini re=true + - name: "Looping over section value_section" + debug: msg="{{item}}" + with_ini: value[1-2] section=value_section file=lookup.ini re=true + - debug: msg="{{item}}" + with_ini: value[1-2] section=section1 file=lookup.ini re=true + register: _ + - debug: var=_ From 09d257e63769854828953fc1e1059622d76665da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yannig=20Perr=C3=A9?= Date: Wed, 5 Aug 2015 12:07:16 +0200 Subject: [PATCH 4/5] Porting ini lookup plugin against ansible v2. --- lib/ansible/{runner/lookup_plugins => plugins/lookup}/ini.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename lib/ansible/{runner/lookup_plugins => plugins/lookup}/ini.py (100%) diff --git a/lib/ansible/runner/lookup_plugins/ini.py b/lib/ansible/plugins/lookup/ini.py similarity index 100% rename from lib/ansible/runner/lookup_plugins/ini.py rename to lib/ansible/plugins/lookup/ini.py From 07fcb50b9bfd26a28e8666ac1751a3122f65564d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yannig=20Perr=C3=A9?= Date: Fri, 7 Aug 2015 09:40:24 +0200 Subject: [PATCH 5/5] Porting ini lookup plugin against ansible v2. --- lib/ansible/plugins/lookup/ini.py | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/lib/ansible/plugins/lookup/ini.py b/lib/ansible/plugins/lookup/ini.py index ebfed750c2d..0c04f06909d 100644 --- a/lib/ansible/plugins/lookup/ini.py +++ b/lib/ansible/plugins/lookup/ini.py @@ -14,19 +14,19 @@ # # You should have received a copy of the GNU General Public License # along with Ansible. If not, see . +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type -from ansible import utils, errors import StringIO import os import codecs import ConfigParser import re -class LookupModule(object): +from ansible.errors import * +from ansible.plugins.lookup import LookupBase - def __init__(self, basedir=None, **kwargs): - self.basedir = basedir - self.cp = ConfigParser.ConfigParser() +class LookupModule(LookupBase): def read_properties(self, filename, key, dflt, is_regexp): config = StringIO.StringIO() @@ -51,13 +51,15 @@ class LookupModule(object): return dflt return value - def run(self, terms, inject=None, **kwargs): - - terms = utils.listify_lookup_plugin_terms(terms, self.basedir, inject) + def run(self, terms, variables=None, **kwargs): if isinstance(terms, basestring): terms = [ terms ] + basedir = self.get_basedir(variables) + self.basedir = basedir + self.cp = ConfigParser.ConfigParser() + ret = [] for term in terms: params = term.split() @@ -80,7 +82,7 @@ class LookupModule(object): except (ValueError, AssertionError), e: raise errors.AnsibleError(e) - path = utils.path_dwim(self.basedir, paramvals['file']) + path = self._loader.path_dwim_relative(basedir, 'files', paramvals['file']) if paramvals['type'] == "properties": var = self.read_properties(path, key, paramvals['default'], paramvals['re']) else: