Merge branch 'default-omit-updated' into devel
This commit is contained in:
commit
f8a30faeee
5 changed files with 99 additions and 18 deletions
|
@ -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
|
In the above example, if the variable 'some_variable' is not defined, the value used will be 5, rather than an error
|
||||||
being raised.
|
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:
|
||||||
|
|
||||||
List Filters
|
List Filters
|
||||||
|
|
|
@ -46,12 +46,17 @@ import connection
|
||||||
from return_data import ReturnData
|
from return_data import ReturnData
|
||||||
from ansible.callbacks import DefaultRunnerCallbacks, vv
|
from ansible.callbacks import DefaultRunnerCallbacks, vv
|
||||||
from ansible.module_common import ModuleReplacer
|
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.cache import FactCache
|
||||||
from ansible.utils import update_hash
|
from ansible.utils import update_hash
|
||||||
|
|
||||||
module_replacer = ModuleReplacer(strip_comments=False)
|
module_replacer = ModuleReplacer(strip_comments=False)
|
||||||
|
|
||||||
|
try:
|
||||||
|
from hashlib import md5 as _md5
|
||||||
|
except ImportError:
|
||||||
|
from md5 import md5 as _md5
|
||||||
|
|
||||||
HAS_ATFORK=True
|
HAS_ATFORK=True
|
||||||
try:
|
try:
|
||||||
from Crypto.Random import atfork
|
from Crypto.Random import atfork
|
||||||
|
@ -202,6 +207,7 @@ class Runner(object):
|
||||||
self.su_user_var = su_user
|
self.su_user_var = su_user
|
||||||
self.su_user = None
|
self.su_user = None
|
||||||
self.su_pass = su_pass
|
self.su_pass = su_pass
|
||||||
|
self.omit_token = '__omit_place_holder__%s' % _md5(os.urandom(64)).hexdigest()
|
||||||
self.vault_pass = vault_pass
|
self.vault_pass = vault_pass
|
||||||
self.no_log = no_log
|
self.no_log = no_log
|
||||||
self.run_once = run_once
|
self.run_once = run_once
|
||||||
|
@ -623,6 +629,7 @@ class Runner(object):
|
||||||
inject['defaults'] = self.default_vars
|
inject['defaults'] = self.default_vars
|
||||||
inject['environment'] = self.environment
|
inject['environment'] = self.environment
|
||||||
inject['playbook_dir'] = os.path.abspath(self.basedir)
|
inject['playbook_dir'] = os.path.abspath(self.basedir)
|
||||||
|
inject['omit'] = self.omit_token
|
||||||
|
|
||||||
# template this one is available, callbacks use this
|
# template this one is available, callbacks use this
|
||||||
delegate_to = self.module_vars.get('delegate_to')
|
delegate_to = self.module_vars.get('delegate_to')
|
||||||
|
@ -740,14 +747,6 @@ class Runner(object):
|
||||||
if self.su_user_var is not None:
|
if self.su_user_var is not None:
|
||||||
self.su_user = template.template(self.basedir, self.su_user_var, inject)
|
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 may be dynamic (but cannot contain {{ ansible_ssh_user }})
|
||||||
module_name = template.template(self.basedir, module_name, inject)
|
module_name = template.template(self.basedir, module_name, inject)
|
||||||
|
|
||||||
|
@ -872,6 +871,11 @@ class Runner(object):
|
||||||
if self._early_needs_tmp_path(module_name, handler):
|
if self._early_needs_tmp_path(module_name, handler):
|
||||||
tmp = self._make_tmp_path(conn)
|
tmp = self._make_tmp_path(conn)
|
||||||
|
|
||||||
|
# allow module args to work as a dictionary
|
||||||
|
# though it is usually a string
|
||||||
|
if isinstance(module_args, dict):
|
||||||
|
module_args = utils.serialize_args(module_args)
|
||||||
|
|
||||||
# render module_args and complex_args templates
|
# render module_args and complex_args templates
|
||||||
try:
|
try:
|
||||||
# When templating module_args, we need to be careful to ensure
|
# When templating module_args, we need to be careful to ensure
|
||||||
|
@ -892,6 +896,24 @@ class Runner(object):
|
||||||
except jinja2.exceptions.UndefinedError, e:
|
except jinja2.exceptions.UndefinedError, e:
|
||||||
raise errors.AnsibleUndefinedVariable("One or more undefined variables: %s" % str(e))
|
raise errors.AnsibleUndefinedVariable("One or more undefined variables: %s" % str(e))
|
||||||
|
|
||||||
|
# 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)
|
result = handler.run(conn, tmp, module_name, module_args, inject, complex_args)
|
||||||
# Code for do until feature
|
# Code for do until feature
|
||||||
|
|
|
@ -31,6 +31,7 @@ from distutils.version import LooseVersion, StrictVersion
|
||||||
from random import SystemRandom
|
from random import SystemRandom
|
||||||
from jinja2.filters import environmentfilter
|
from jinja2.filters import environmentfilter
|
||||||
|
|
||||||
|
|
||||||
def to_nice_yaml(*a, **kw):
|
def to_nice_yaml(*a, **kw):
|
||||||
'''Make verbose, human readable yaml'''
|
'''Make verbose, human readable yaml'''
|
||||||
return yaml.safe_dump(*a, indent=4, allow_unicode=True, default_flow_style=False, **kw)
|
return yaml.safe_dump(*a, indent=4, allow_unicode=True, default_flow_style=False, **kw)
|
||||||
|
@ -234,6 +235,7 @@ def rand(environment, end, start=None, step=None):
|
||||||
else:
|
else:
|
||||||
raise errors.AnsibleFilterError('random can only be used on sequences and integers')
|
raise errors.AnsibleFilterError('random can only be used on sequences and integers')
|
||||||
|
|
||||||
|
|
||||||
class FilterModule(object):
|
class FilterModule(object):
|
||||||
''' Ansible core jinja2 filters '''
|
''' Ansible core jinja2 filters '''
|
||||||
|
|
||||||
|
@ -306,4 +308,3 @@ class FilterModule(object):
|
||||||
# random numbers
|
# random numbers
|
||||||
'random': rand,
|
'random': rand,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -61,6 +61,7 @@ LOOKUP_REGEX = re.compile(r'lookup\s*\(')
|
||||||
PRINT_CODE_REGEX = re.compile(r'(?:{[{%]|[%}]})')
|
PRINT_CODE_REGEX = re.compile(r'(?:{[{%]|[%}]})')
|
||||||
CODE_REGEX = re.compile(r'(?:{%|%})')
|
CODE_REGEX = re.compile(r'(?:{%|%})')
|
||||||
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import json
|
import json
|
||||||
except ImportError:
|
except ImportError:
|
||||||
|
@ -110,6 +111,7 @@ try:
|
||||||
except ImportError:
|
except ImportError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
###############################################################
|
###############################################################
|
||||||
# Abstractions around keyczar
|
# Abstractions around keyczar
|
||||||
###############################################################
|
###############################################################
|
||||||
|
@ -543,6 +545,18 @@ def parse_json(raw_data, from_remote=False, from_inventory=False):
|
||||||
|
|
||||||
return results
|
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):
|
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,
|
merges either a dictionary or string of k=v pairs with another string of k=v pairs,
|
||||||
|
@ -557,14 +571,7 @@ def merge_module_args(current_args, new_args):
|
||||||
elif isinstance(new_args, basestring):
|
elif isinstance(new_args, basestring):
|
||||||
new_args_kv = parse_kv(new_args)
|
new_args_kv = parse_kv(new_args)
|
||||||
final_args.update(new_args_kv)
|
final_args.update(new_args_kv)
|
||||||
# then we re-assemble into a string
|
return serialize_args(final_args)
|
||||||
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()
|
|
||||||
|
|
||||||
def parse_yaml(data, path_hint=None):
|
def parse_yaml(data, path_hint=None):
|
||||||
''' convert a yaml string to a data structure. Also supports JSON, ssssssh!!!'''
|
''' convert a yaml string to a data structure. Also supports JSON, ssssssh!!!'''
|
||||||
|
|
|
@ -172,3 +172,33 @@
|
||||||
assert:
|
assert:
|
||||||
that:
|
that:
|
||||||
- nested_include_var is undefined
|
- 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 is defined
|
||||||
|
|
||||||
|
- 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"
|
||||||
|
should_not_omit_3="__omit_place_holder__afb6b9bc3d20bfeaa00a1b23a5930f89"
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- yo == 'whatsup'
|
||||||
|
- eggs is undefined
|
||||||
|
- default_omitted is undefined
|
||||||
|
- should_not_omit_1 is defined
|
||||||
|
- should_not_omit_2 is defined
|
||||||
|
- should_not_omit_3 == "__omit_place_holder__afb6b9bc3d20bfeaa00a1b23a5930f89"
|
||||||
|
|
Loading…
Reference in a new issue