Commit c32b8335 by Feanil Patel

Don't convert nulls to strings.

This change is similar to https://github.com/ansible/ansible/pull/10465

It extends the logic there to also support none types.  Right now if you have
a '!!null' in yaml, and that var gets passed around, it will get converted to
a string.

eg. defaults/main.yml
```
ENABLE_AWESOME_FEATURE: !!null # Yaml Null
OTHER_CONFIG:
  secret1: "so_secret"
  secret2: "even_more_secret"

CONFIG:
  hostname: "some_hostname"
  features:
    awesame_feature: "{{ ENABLE_AWESOME_FEATURE}}"
  secrets: "{{ OTHER_CONFIG }}"
```

If you output `CONFIG` to json or yaml, the feature flag would get represented in the output
as a string instead of as a null, but secrets would get represented as a dictionary.  This is
a mis-match in behaviour where some "types" are retained and others are not.  This change
should fix the issue.

I also updated the template test to test for this and made the changes to v2.

Added a changelog entry specifically for the change from empty string to null as the default.

Made the null representation configurable.

It still defaults to the python NoneType but can be overriden to be an emptystring by updating
the DEFAULT_NULL_REPRESENTATION config.

Conflicts:
	lib/ansible/constants.py

The way get_config is implemented change from 1.9 to devel.  I matched my code to what was there.
parent bde7b7c3
...@@ -8,6 +8,37 @@ Major Changes: ...@@ -8,6 +8,37 @@ Major Changes:
- template code now retains types for bools and Numbers instead of turning them into strings - template code now retains types for bools and Numbers instead of turning them into strings
- If you need the old behaviour, quote the value and it will get passed around as a string - If you need the old behaviour, quote the value and it will get passed around as a string
* Introducing the new block/rescue/always directives, allow for making task blocks and introducing exception like semantics
* New strategy plugins, allow to control the flow of execution of tasks per play, the default will be the same as before
* Improved error handling, now you get much more detailed parser messages. General exception handling and display has been revamped.
* Task includes now get evaluated during execution, end behaviour will be the same but it now allows for more dynamic includes and options.
* First feature of the more dynamic includes is that with_ loops are now usable with them.
* callback, connection and lookup plugin APIs have changed, some will require modification to work with new version
* callbacks are now shipped in the active directory and don't need to be copied, just whitelisted in ansible.cfg
* Many API changes, this will break those currently using it directly, but the new API is much easier to use and test
* Settings are now more inheritable, what you set at play, block or role will be automatically inhertited by the contained.
This allows for new features to automatically be settable at all levels, previously we had to manually code this
* Many more tests, new API makes things more testable and we took advantage of it
* big_ip modules now support turning off ssl certificate validation (use only for self signed)
* template code now retains types for bools and numbers instead of turning them into strings.
If you need the old behaviour, quote the value and it will get passed around as a string
* Consolidated code from modules using urllib2 to normalize features, TLS and SNI support
* Consiidated code from modules using urllib2 to normalize features, TLS and SNI support
* added meta: refresh_inventory to force rereading the inventory in a play
* template code now retains types for bools, and Numbers instead of turning them into strings
If you need the old behaviour, quote the value and it will get passed around as a string. In the
case of nulls, the output used to be an empty string.
* Empty variables and variables set to null in yaml will no longer be converted to empty strings.
They will retain the value of `None`. To go back to the old behaviour, you can override
the `null_representation` setting to an empty string in your config file or by setting the
`ANSIBLE_NULL_REPRESENTATION` environment variable.
Deprecated Modules (new ones in parens):
* ec2_ami_search (ec2_ami_find)
* quantum_network (os_network)
* glance_image
* nova_compute (os_server)
* quantum_floating_ip (os_floating_ip)
New Modules: New Modules:
......
...@@ -31,7 +31,7 @@ def mk_boolean(value): ...@@ -31,7 +31,7 @@ def mk_boolean(value):
else: else:
return False return False
def get_config(p, section, key, env_var, default, boolean=False, integer=False, floating=False, islist=False): def get_config(p, section, key, env_var, default, boolean=False, integer=False, floating=False, islist=False, isnone=False):
''' return a configuration variable with casting ''' ''' return a configuration variable with casting '''
value = _get_config(p, section, key, env_var, default) value = _get_config(p, section, key, env_var, default)
if boolean: if boolean:
...@@ -42,6 +42,9 @@ def get_config(p, section, key, env_var, default, boolean=False, integer=False, ...@@ -42,6 +42,9 @@ def get_config(p, section, key, env_var, default, boolean=False, integer=False,
return float(value) return float(value)
if value and islist: if value and islist:
return [x.strip() for x in value.split(',')] return [x.strip() for x in value.split(',')]
if value and isnone:
if value == "None":
return None
return value return value
def _get_config(p, section, key, env_var, default): def _get_config(p, section, key, env_var, default):
...@@ -181,6 +184,7 @@ DEFAULT_FORCE_HANDLERS = get_config(p, DEFAULTS, 'force_handlers', 'ANSI ...@@ -181,6 +184,7 @@ DEFAULT_FORCE_HANDLERS = get_config(p, DEFAULTS, 'force_handlers', 'ANSI
RETRY_FILES_ENABLED = get_config(p, DEFAULTS, 'retry_files_enabled', 'ANSIBLE_RETRY_FILES_ENABLED', True, boolean=True) RETRY_FILES_ENABLED = get_config(p, DEFAULTS, 'retry_files_enabled', 'ANSIBLE_RETRY_FILES_ENABLED', True, boolean=True)
RETRY_FILES_SAVE_PATH = get_config(p, DEFAULTS, 'retry_files_save_path', 'ANSIBLE_RETRY_FILES_SAVE_PATH', '~/') RETRY_FILES_SAVE_PATH = get_config(p, DEFAULTS, 'retry_files_save_path', 'ANSIBLE_RETRY_FILES_SAVE_PATH', '~/')
DEFAULT_NULL_REPRESENTATION = get_config(p, DEFAULTS, 'null_representation', 'ANSIBLE_NULL_REPRESENTATION', None, isnone=True)
# CONNECTION RELATED # CONNECTION RELATED
ANSIBLE_SSH_ARGS = get_config(p, 'ssh_connection', 'ssh_args', 'ANSIBLE_SSH_ARGS', None) ANSIBLE_SSH_ARGS = get_config(p, 'ssh_connection', 'ssh_args', 'ANSIBLE_SSH_ARGS', None)
......
...@@ -32,6 +32,7 @@ import pwd ...@@ -32,6 +32,7 @@ import pwd
import ast import ast
import traceback import traceback
from numbers import Number from numbers import Number
from types import NoneType
from ansible.utils.string_functions import count_newlines_from_end from ansible.utils.string_functions import count_newlines_from_end
from ansible.utils import to_bytes, to_unicode from ansible.utils import to_bytes, to_unicode
...@@ -343,7 +344,7 @@ def template_from_string(basedir, data, vars, fail_on_undefined=False): ...@@ -343,7 +344,7 @@ def template_from_string(basedir, data, vars, fail_on_undefined=False):
var_name = only_one.group(1) var_name = only_one.group(1)
if var_name in vars: if var_name in vars:
resolved_val = vars[var_name] resolved_val = vars[var_name]
if isinstance(resolved_val, (bool, Number)): if isinstance(resolved_val, (bool, Number, NoneType)):
return resolved_val return resolved_val
def my_finalize(thing): def my_finalize(thing):
......
...@@ -3,6 +3,7 @@ templated_var_loaded ...@@ -3,6 +3,7 @@ templated_var_loaded
{ {
"bool": true, "bool": true,
"multi_part": "1Foo", "multi_part": "1Foo",
"null_type": null,
"number": 5, "number": 5,
"string_num": "5" "string_num": "5"
} }
...@@ -5,10 +5,12 @@ string_num: "5" ...@@ -5,10 +5,12 @@ string_num: "5"
bool_var: true bool_var: true
part_1: 1 part_1: 1
part_2: "Foo" part_2: "Foo"
null_type: !!null
templated_dict: templated_dict:
number: "{{ number_var }}" number: "{{ number_var }}"
string_num: "{{ string_num }}" string_num: "{{ string_num }}"
null_type: "{{ null_type }}"
bool: "{{ bool_var }}" bool: "{{ bool_var }}"
multi_part: "{{ part_1 }}{{ part_2 }}" multi_part: "{{ part_1 }}{{ part_2 }}"
...@@ -33,6 +33,7 @@ from ansible.template.vars import AnsibleJ2Vars ...@@ -33,6 +33,7 @@ from ansible.template.vars import AnsibleJ2Vars
from ansible.utils.debug import debug from ansible.utils.debug import debug
from numbers import Number from numbers import Number
from types import NoneType
__all__ = ['Templar'] __all__ = ['Templar']
...@@ -145,6 +146,8 @@ class Templar: ...@@ -145,6 +146,8 @@ class Templar:
resolved_val = self._available_vars[var_name] resolved_val = self._available_vars[var_name]
if isinstance(resolved_val, NON_TEMPLATED_TYPES): if isinstance(resolved_val, NON_TEMPLATED_TYPES):
return resolved_val return resolved_val
elif isinstance(resolved_val, NoneType):
return C.DEFAULT_NULL_REPRESENTATION
result = self._do_template(variable, preserve_trailing_newlines=preserve_trailing_newlines) result = self._do_template(variable, preserve_trailing_newlines=preserve_trailing_newlines)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment