From 0b4d7f1574736ebe0e936387186093c52d6d3423 Mon Sep 17 00:00:00 2001 From: Victor Lin Date: Mon, 28 Jul 2014 18:21:31 -0700 Subject: [PATCH 1/9] Implement default omit filter --- lib/ansible/runner/__init__.py | 21 +++++++++++++-------- lib/ansible/runner/filter_plugins/core.py | 17 +++++++++++++++-- lib/ansible/utils/__init__.py | 6 ++++++ 3 files changed, 34 insertions(+), 10 deletions(-) diff --git a/lib/ansible/runner/__init__.py b/lib/ansible/runner/__init__.py index a63bf426533..ea8cbc37a48 100644 --- a/lib/ansible/runner/__init__.py +++ b/lib/ansible/runner/__init__.py @@ -49,6 +49,7 @@ from ansible.module_common import ModuleReplacer from ansible.module_utils.splitter import split_args from ansible.cache import FactCache from ansible.utils import update_hash +from ansible.utils import OMIT_PLACE_HOLDER module_replacer = ModuleReplacer(strip_comments=False) @@ -740,14 +741,6 @@ class Runner(object): if self.su_user_var is not None: self.su_user = template.template(self.basedir, self.su_user_var, inject) - # allow module args to work as a dictionary - # though it is usually a string - new_args = "" - if type(module_args) == dict: - for (k,v) in module_args.iteritems(): - new_args = new_args + "%s='%s' " % (k,v) - module_args = new_args - # module_name may be dynamic (but cannot contain {{ ansible_ssh_user }}) module_name = template.template(self.basedir, module_name, inject) @@ -872,6 +865,18 @@ class Runner(object): if self._early_needs_tmp_path(module_name, handler): tmp = self._make_tmp_path(conn) + # allow module args to work as a dictionary + # though it is usually a string + if type(module_args) == dict: + new_args = [] + for (k, v) in module_args.iteritems(): + # see if the value is OMIT_PLACE_HOLDER, if it is, skip it + arg_value = template.template(self.basedir, v, inject, fail_on_undefined=self.error_on_undefined_vars) + if arg_value.strip() == OMIT_PLACE_HOLDER: + continue + new_args.append("%s='%s'" % (k, v)) + module_args = ' '.join(new_args) + # render module_args and complex_args templates try: # When templating module_args, we need to be careful to ensure diff --git a/lib/ansible/runner/filter_plugins/core.py b/lib/ansible/runner/filter_plugins/core.py index 7ca0f937420..0b08582fb23 100644 --- a/lib/ansible/runner/filter_plugins/core.py +++ b/lib/ansible/runner/filter_plugins/core.py @@ -15,6 +15,7 @@ # You should have received a copy of the GNU General Public License # along with Ansible. If not, see . +import os import base64 import json import os.path @@ -25,12 +26,14 @@ import glob import re import collections import operator as py_operator +import hashlib from ansible import errors -from ansible.utils import md5s +from ansible.utils import md5s, OMIT_PLACE_HOLDER from distutils.version import LooseVersion, StrictVersion from random import SystemRandom from jinja2.filters import environmentfilter + def to_nice_yaml(*a, **kw): '''Make verbose, human readable yaml''' return yaml.safe_dump(*a, indent=4, allow_unicode=True, default_flow_style=False, **kw) @@ -234,6 +237,15 @@ def rand(environment, end, start=None, step=None): else: raise errors.AnsibleFilterError('random can only be used on sequences and integers') +def default_omit(a): + try: + a + except NameError: + return OMIT_PLACE_HOLDER + else: + return a + + class FilterModule(object): ''' Ansible core jinja2 filters ''' @@ -305,5 +317,6 @@ class FilterModule(object): # random numbers 'random': rand, - } + 'default_omit': default_omit, + } diff --git a/lib/ansible/utils/__init__.py b/lib/ansible/utils/__init__.py index 131a3e315df..8030fa6318f 100644 --- a/lib/ansible/utils/__init__.py +++ b/lib/ansible/utils/__init__.py @@ -61,6 +61,7 @@ LOOKUP_REGEX = re.compile(r'lookup\s*\(') PRINT_CODE_REGEX = re.compile(r'(?:{[{%]|[%}]})') CODE_REGEX = re.compile(r'(?:{%|%})') + try: import json except ImportError: @@ -110,6 +111,11 @@ try: except ImportError: pass + +OMIT_PLACE_HOLDER = ( + '__omit_place_holder__%s' % _md5(os.urandom(64)).hexdigest() +) + ############################################################### # Abstractions around keyczar ############################################################### From bce6642ab68adc292d0bf9a77ef80f72dc873fe7 Mon Sep 17 00:00:00 2001 From: Victor Lin Date: Mon, 28 Jul 2014 18:44:40 -0700 Subject: [PATCH 2/9] Implement default omit correctly --- lib/ansible/runner/__init__.py | 11 +++++++---- lib/ansible/runner/filter_plugins/core.py | 9 ++++----- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/lib/ansible/runner/__init__.py b/lib/ansible/runner/__init__.py index ea8cbc37a48..fd80d7833ed 100644 --- a/lib/ansible/runner/__init__.py +++ b/lib/ansible/runner/__init__.py @@ -870,10 +870,6 @@ class Runner(object): if type(module_args) == dict: new_args = [] for (k, v) in module_args.iteritems(): - # see if the value is OMIT_PLACE_HOLDER, if it is, skip it - arg_value = template.template(self.basedir, v, inject, fail_on_undefined=self.error_on_undefined_vars) - if arg_value.strip() == OMIT_PLACE_HOLDER: - continue new_args.append("%s='%s'" % (k, v)) module_args = ' '.join(new_args) @@ -897,6 +893,13 @@ class Runner(object): except jinja2.exceptions.UndefinedError, e: raise errors.AnsibleUndefinedVariable("One or more undefined variables: %s" % str(e)) + # filter omitted arguments out + new_complex_args = {} + for key, value in complex_args.iteritems(): + if value == OMIT_PLACE_HOLDER: + continue + new_complex_args[key] = value + complex_args = new_complex_args result = handler.run(conn, tmp, module_name, module_args, inject, complex_args) # Code for do until feature diff --git a/lib/ansible/runner/filter_plugins/core.py b/lib/ansible/runner/filter_plugins/core.py index 0b08582fb23..1096cf378ee 100644 --- a/lib/ansible/runner/filter_plugins/core.py +++ b/lib/ansible/runner/filter_plugins/core.py @@ -32,6 +32,7 @@ from ansible.utils import md5s, OMIT_PLACE_HOLDER from distutils.version import LooseVersion, StrictVersion from random import SystemRandom from jinja2.filters import environmentfilter +from jinja2.runtime import Undefined def to_nice_yaml(*a, **kw): @@ -237,13 +238,11 @@ def rand(environment, end, start=None, step=None): else: raise errors.AnsibleFilterError('random can only be used on sequences and integers') + def default_omit(a): - try: - a - except NameError: + if isinstance(a, Undefined): return OMIT_PLACE_HOLDER - else: - return a + return a class FilterModule(object): From 559c04f324221ac6bf9031657065211612d250b4 Mon Sep 17 00:00:00 2001 From: Victor Lin Date: Fri, 1 Aug 2014 22:11:42 -0700 Subject: [PATCH 3/9] Implement omit for module args, also add tests for it --- lib/ansible/runner/__init__.py | 25 ++++++++-------- lib/ansible/runner/filter_plugins/core.py | 9 ------ lib/ansible/utils/__init__.py | 4 +++ .../roles/test_good_parsing/tasks/main.yml | 29 +++++++++++++++++++ 4 files changed, 46 insertions(+), 21 deletions(-) diff --git a/lib/ansible/runner/__init__.py b/lib/ansible/runner/__init__.py index fd80d7833ed..ba948f18f39 100644 --- a/lib/ansible/runner/__init__.py +++ b/lib/ansible/runner/__init__.py @@ -624,6 +624,7 @@ class Runner(object): inject['defaults'] = self.default_vars inject['environment'] = self.environment inject['playbook_dir'] = os.path.abspath(self.basedir) + inject['omit'] = OMIT_PLACE_HOLDER # template this one is available, callbacks use this delegate_to = self.module_vars.get('delegate_to') @@ -867,11 +868,8 @@ class Runner(object): # allow module args to work as a dictionary # though it is usually a string - if type(module_args) == dict: - new_args = [] - for (k, v) in module_args.iteritems(): - new_args.append("%s='%s'" % (k, v)) - module_args = ' '.join(new_args) + if isinstance(module_args, dict): + module_args = utils.serialize_args(module_args) # render module_args and complex_args templates try: @@ -893,13 +891,16 @@ class Runner(object): except jinja2.exceptions.UndefinedError, e: raise errors.AnsibleUndefinedVariable("One or more undefined variables: %s" % str(e)) - # filter omitted arguments out - new_complex_args = {} - for key, value in complex_args.iteritems(): - if value == OMIT_PLACE_HOLDER: - continue - new_complex_args[key] = value - complex_args = new_complex_args + def not_omitted(item): + return item[1] != OMIT_PLACE_HOLDER + + if module_name not in ['shell', 'command']: + # filter omitted arguments out from complex_args + complex_args = dict(filter(not_omitted, complex_args.iteritems())) + # filter omitted arguments out from module_args + module_kv = utils.parse_kv(module_args) + module_kv = dict(filter(not_omitted, module_kv.iteritems())) + module_args = utils.serialize_args(module_kv) result = handler.run(conn, tmp, module_name, module_args, inject, complex_args) # Code for do until feature diff --git a/lib/ansible/runner/filter_plugins/core.py b/lib/ansible/runner/filter_plugins/core.py index 1096cf378ee..7935cf19b66 100644 --- a/lib/ansible/runner/filter_plugins/core.py +++ b/lib/ansible/runner/filter_plugins/core.py @@ -32,7 +32,6 @@ from ansible.utils import md5s, OMIT_PLACE_HOLDER from distutils.version import LooseVersion, StrictVersion from random import SystemRandom from jinja2.filters import environmentfilter -from jinja2.runtime import Undefined def to_nice_yaml(*a, **kw): @@ -239,12 +238,6 @@ def rand(environment, end, start=None, step=None): raise errors.AnsibleFilterError('random can only be used on sequences and integers') -def default_omit(a): - if isinstance(a, Undefined): - return OMIT_PLACE_HOLDER - return a - - class FilterModule(object): ''' Ansible core jinja2 filters ''' @@ -316,6 +309,4 @@ class FilterModule(object): # random numbers 'random': rand, - - 'default_omit': default_omit, } diff --git a/lib/ansible/utils/__init__.py b/lib/ansible/utils/__init__.py index 8030fa6318f..7a50606d201 100644 --- a/lib/ansible/utils/__init__.py +++ b/lib/ansible/utils/__init__.py @@ -787,6 +787,10 @@ def _validate_both_dicts(a, b): "failed to combine variables, expected dicts but got a '%s' and a '%s'" % (type(a).__name__, type(b).__name__) ) +def serialize_args(args): + ''' convert a dict to a string of key/value items ''' + return ' '.join("%s='%s'" % item for item in args.iteritems()) + def merge_hash(a, b): ''' recursively merges hash b into a keys from b take precedence over keys from a ''' diff --git a/test/integration/roles/test_good_parsing/tasks/main.yml b/test/integration/roles/test_good_parsing/tasks/main.yml index 6461d8e7728..63895df4682 100644 --- a/test/integration/roles/test_good_parsing/tasks/main.yml +++ b/test/integration/roles/test_good_parsing/tasks/main.yml @@ -172,3 +172,32 @@ assert: that: - nested_include_var is undefined + +- name: test omit in complex args + set_fact: + foo: bar + spam: "{{ omit }}" + should_not_omit: "prefix{{ omit }}" + +- assert: + that: + - foo == 'bar' + - spam is undefined + - should_not_omit == "prefix{{ omit }}" + +- name: test omit in module args + set_fact: > + yo=whatsup + eggs="{{ omit }}" + default_omitted="{{ not_exists|default(omit) }}" + should_not_omit_1="prefix{{ omit }}" + should_not_omit_2="{{ omit }}suffix" + +- assert: + that: + - yo == 'whatsup' + - eggs is undefined + - default_omitted is undefined + - should_not_omit_1 == "prefix{{ omit }}" + - should_not_omit_2 == "{{ omit }}suffix" + From fe88ba7c8dce89ca315e7652313f575501212160 Mon Sep 17 00:00:00 2001 From: Victor Lin Date: Fri, 1 Aug 2014 22:13:04 -0700 Subject: [PATCH 4/9] Undo changes to filter module --- lib/ansible/runner/filter_plugins/core.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/ansible/runner/filter_plugins/core.py b/lib/ansible/runner/filter_plugins/core.py index 7935cf19b66..61b80bce2c5 100644 --- a/lib/ansible/runner/filter_plugins/core.py +++ b/lib/ansible/runner/filter_plugins/core.py @@ -15,7 +15,6 @@ # You should have received a copy of the GNU General Public License # along with Ansible. If not, see . -import os import base64 import json import os.path @@ -26,9 +25,8 @@ import glob import re import collections import operator as py_operator -import hashlib from ansible import errors -from ansible.utils import md5s, OMIT_PLACE_HOLDER +from ansible.utils import md5s from distutils.version import LooseVersion, StrictVersion from random import SystemRandom from jinja2.filters import environmentfilter From 6260635c65ec347376523d3b60981098e6a39a25 Mon Sep 17 00:00:00 2001 From: Victor Lin Date: Fri, 1 Aug 2014 22:18:04 -0700 Subject: [PATCH 5/9] add new test case --- test/integration/roles/test_good_parsing/tasks/main.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/integration/roles/test_good_parsing/tasks/main.yml b/test/integration/roles/test_good_parsing/tasks/main.yml index 63895df4682..96701b7f609 100644 --- a/test/integration/roles/test_good_parsing/tasks/main.yml +++ b/test/integration/roles/test_good_parsing/tasks/main.yml @@ -192,6 +192,7 @@ default_omitted="{{ not_exists|default(omit) }}" should_not_omit_1="prefix{{ omit }}" should_not_omit_2="{{ omit }}suffix" + should_not_omit_3="__omit_place_holder__afb6b9bc3d20bfeaa00a1b23a5930f89" - assert: that: @@ -200,4 +201,4 @@ - default_omitted is undefined - should_not_omit_1 == "prefix{{ omit }}" - should_not_omit_2 == "{{ omit }}suffix" - + - should_not_omit_3 == "__omit_place_holder__afb6b9bc3d20bfeaa00a1b23a5930f89" From e99db078b4748c02bff86648488c507ddef7717c Mon Sep 17 00:00:00 2001 From: James Cammarata Date: Tue, 5 Aug 2014 12:20:10 -0500 Subject: [PATCH 6/9] Additional fixes for the new omit parameter variable --- lib/ansible/runner/__init__.py | 15 +++++++++++++-- lib/ansible/utils/__init__.py | 8 -------- .../roles/test_good_parsing/tasks/main.yml | 6 +++--- 3 files changed, 16 insertions(+), 13 deletions(-) diff --git a/lib/ansible/runner/__init__.py b/lib/ansible/runner/__init__.py index ba948f18f39..67638e19b92 100644 --- a/lib/ansible/runner/__init__.py +++ b/lib/ansible/runner/__init__.py @@ -53,6 +53,11 @@ from ansible.utils import OMIT_PLACE_HOLDER module_replacer = ModuleReplacer(strip_comments=False) +try: + from hashlib import md5 as _md5 +except ImportError: + from md5 import md5 as _md5 + HAS_ATFORK=True try: from Crypto.Random import atfork @@ -203,6 +208,7 @@ class Runner(object): self.su_user_var = su_user self.su_user = None self.su_pass = su_pass + self.omit_token = '__omit_place_holder__%s' % _md5(os.urandom(64)).hexdigest() self.vault_pass = vault_pass self.no_log = no_log self.run_once = run_once @@ -623,8 +629,13 @@ class Runner(object): inject['vars'] = self.module_vars inject['defaults'] = self.default_vars inject['environment'] = self.environment +<<<<<<< HEAD inject['playbook_dir'] = os.path.abspath(self.basedir) inject['omit'] = OMIT_PLACE_HOLDER +======= + inject['playbook_dir'] = self.basedir + inject['omit'] = self.omit_token +>>>>>>> Additional fixes for the new omit parameter variable # template this one is available, callbacks use this delegate_to = self.module_vars.get('delegate_to') @@ -892,9 +903,9 @@ class Runner(object): raise errors.AnsibleUndefinedVariable("One or more undefined variables: %s" % str(e)) def not_omitted(item): - return item[1] != OMIT_PLACE_HOLDER + return item[1] != self.omit_token - if module_name not in ['shell', 'command']: + if module_name not in ['shell', 'command', 'include_vars']: # filter omitted arguments out from complex_args complex_args = dict(filter(not_omitted, complex_args.iteritems())) # filter omitted arguments out from module_args diff --git a/lib/ansible/utils/__init__.py b/lib/ansible/utils/__init__.py index 7a50606d201..a2500c407df 100644 --- a/lib/ansible/utils/__init__.py +++ b/lib/ansible/utils/__init__.py @@ -112,10 +112,6 @@ except ImportError: pass -OMIT_PLACE_HOLDER = ( - '__omit_place_holder__%s' % _md5(os.urandom(64)).hexdigest() -) - ############################################################### # Abstractions around keyczar ############################################################### @@ -787,10 +783,6 @@ def _validate_both_dicts(a, b): "failed to combine variables, expected dicts but got a '%s' and a '%s'" % (type(a).__name__, type(b).__name__) ) -def serialize_args(args): - ''' convert a dict to a string of key/value items ''' - return ' '.join("%s='%s'" % item for item in args.iteritems()) - def merge_hash(a, b): ''' recursively merges hash b into a keys from b take precedence over keys from a ''' diff --git a/test/integration/roles/test_good_parsing/tasks/main.yml b/test/integration/roles/test_good_parsing/tasks/main.yml index 96701b7f609..27475ce0f53 100644 --- a/test/integration/roles/test_good_parsing/tasks/main.yml +++ b/test/integration/roles/test_good_parsing/tasks/main.yml @@ -183,7 +183,7 @@ that: - foo == 'bar' - spam is undefined - - should_not_omit == "prefix{{ omit }}" + - should_not_omit is defined - name: test omit in module args set_fact: > @@ -199,6 +199,6 @@ - yo == 'whatsup' - eggs is undefined - default_omitted is undefined - - should_not_omit_1 == "prefix{{ omit }}" - - should_not_omit_2 == "{{ omit }}suffix" + - should_not_omit_1 is defined + - should_not_omit_2 is defined - should_not_omit_3 == "__omit_place_holder__afb6b9bc3d20bfeaa00a1b23a5930f89" From 04da466c7b9a1279ca4e107a20672521cc28d2b0 Mon Sep 17 00:00:00 2001 From: James Cammarata Date: Wed, 27 Aug 2014 10:20:07 -0500 Subject: [PATCH 7/9] Fixing up serialize_args utility function --- lib/ansible/runner/__init__.py | 6 ------ lib/ansible/utils/__init__.py | 21 +++++++++++++-------- 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/lib/ansible/runner/__init__.py b/lib/ansible/runner/__init__.py index 67638e19b92..afff26071ea 100644 --- a/lib/ansible/runner/__init__.py +++ b/lib/ansible/runner/__init__.py @@ -49,7 +49,6 @@ from ansible.module_common import ModuleReplacer from ansible.module_utils.splitter import split_args from ansible.cache import FactCache from ansible.utils import update_hash -from ansible.utils import OMIT_PLACE_HOLDER module_replacer = ModuleReplacer(strip_comments=False) @@ -629,13 +628,8 @@ class Runner(object): inject['vars'] = self.module_vars inject['defaults'] = self.default_vars inject['environment'] = self.environment -<<<<<<< HEAD inject['playbook_dir'] = os.path.abspath(self.basedir) - inject['omit'] = OMIT_PLACE_HOLDER -======= - inject['playbook_dir'] = self.basedir inject['omit'] = self.omit_token ->>>>>>> Additional fixes for the new omit parameter variable # template this one is available, callbacks use this delegate_to = self.module_vars.get('delegate_to') diff --git a/lib/ansible/utils/__init__.py b/lib/ansible/utils/__init__.py index a2500c407df..aa0a1b8ee71 100644 --- a/lib/ansible/utils/__init__.py +++ b/lib/ansible/utils/__init__.py @@ -545,6 +545,18 @@ def parse_json(raw_data, from_remote=False, from_inventory=False): return results +def serialize_args(args): + ''' + Flattens a dictionary args to a k=v string + ''' + module_args = "" + for (k,v) in args.iteritems(): + if isinstance(v, basestring): + module_args = "%s=%s %s" % (k, pipes.quote(v), module_args) + elif isinstance(v, bool): + module_args = "%s=%s %s" % (k, str(v), module_args) + return module_args.strip() + def merge_module_args(current_args, new_args): ''' merges either a dictionary or string of k=v pairs with another string of k=v pairs, @@ -559,14 +571,7 @@ def merge_module_args(current_args, new_args): elif isinstance(new_args, basestring): new_args_kv = parse_kv(new_args) final_args.update(new_args_kv) - # then we re-assemble into a string - module_args = "" - for (k,v) in final_args.iteritems(): - if isinstance(v, basestring): - module_args = "%s=%s %s" % (k, pipes.quote(v), module_args) - elif isinstance(v, bool): - module_args = "%s=%s %s" % (k, str(v), module_args) - return module_args.strip() + return serialize_args(final_args) def parse_yaml(data, path_hint=None): ''' convert a yaml string to a data structure. Also supports JSON, ssssssh!!!''' From a6ad37466808250fbd623cd86d8db4a45dd8b32d Mon Sep 17 00:00:00 2001 From: James Cammarata Date: Wed, 27 Aug 2014 11:51:03 -0500 Subject: [PATCH 8/9] Use split_args directly to check for omitted params --- lib/ansible/runner/__init__.py | 30 +++++++++++++++++++----------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/lib/ansible/runner/__init__.py b/lib/ansible/runner/__init__.py index afff26071ea..b7bfc7bd3c0 100644 --- a/lib/ansible/runner/__init__.py +++ b/lib/ansible/runner/__init__.py @@ -46,7 +46,7 @@ import connection from return_data import ReturnData from ansible.callbacks import DefaultRunnerCallbacks, vv from ansible.module_common import ModuleReplacer -from ansible.module_utils.splitter import split_args +from ansible.module_utils.splitter import split_args, unquote from ansible.cache import FactCache from ansible.utils import update_hash @@ -896,16 +896,24 @@ class Runner(object): except jinja2.exceptions.UndefinedError, e: raise errors.AnsibleUndefinedVariable("One or more undefined variables: %s" % str(e)) - def not_omitted(item): - return item[1] != self.omit_token - - if module_name not in ['shell', 'command', 'include_vars']: - # filter omitted arguments out from complex_args - complex_args = dict(filter(not_omitted, complex_args.iteritems())) - # filter omitted arguments out from module_args - module_kv = utils.parse_kv(module_args) - module_kv = dict(filter(not_omitted, module_kv.iteritems())) - module_args = utils.serialize_args(module_kv) + # filter omitted arguments out from complex_args + if complex_args: + complex_args = dict(filter(lambda x: x[1] != self.omit_token, complex_args.iteritems())) + + # Filter omitted arguments out from module_args. + # We do this with split_args instead of parse_kv to ensure + # that things are not unquoted/requoted incorrectly + args = split_args(module_args) + final_args = [] + for arg in args: + if '=' in arg: + k,v = arg.split('=', 1) + if unquote(v) != self.omit_token: + final_args.append(arg) + else: + # not a k=v param, append it + final_args.append(arg) + module_args = ' '.join(final_args) result = handler.run(conn, tmp, module_name, module_args, inject, complex_args) # Code for do until feature From 35dac66afa10e5c8cfb62257d82de47d51b78920 Mon Sep 17 00:00:00 2001 From: James Cammarata Date: Wed, 27 Aug 2014 12:41:49 -0500 Subject: [PATCH 9/9] Adding docs for default+omit --- docsite/rst/playbooks_variables.rst | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/docsite/rst/playbooks_variables.rst b/docsite/rst/playbooks_variables.rst index 82d28c71eef..1e61cecbb01 100644 --- a/docsite/rst/playbooks_variables.rst +++ b/docsite/rst/playbooks_variables.rst @@ -180,6 +180,27 @@ Jinja2 provides a useful 'default' filter, that is often a better approach to fa In the above example, if the variable 'some_variable' is not defined, the value used will be 5, rather than an error being raised. + +.. _omitting_undefined_variables: + +Omitting Undefined Variables and Parameters +------------------------------------------- + +As of Ansible 1.8, it is possible to use the default filter to omit variables and module parameters using the special +`omit` variable:: + + - name: touch files with an optional mode + file: dest={{item.path}} state=touch mode={{item.mode|default(omit)}} + with_items: + - path: /tmp/foo + - path: /tmp/bar + - path: /tmp/baz + mode: "0444" + +For the first two files in the list, the default mode will be determined by the umask of the system as the `mode=` +parameter will not be sent to the file module while the final file will receive the `mode=0444` option. + + .. _list_filters: List Filters