Add option to use a remote source

The config template action plugin assumes that the local node is
supplying a configuration file that will be used to copy content
to a target node. This change gives the module the ability to
change configuration files on target nodes using all remote
sources. The new boolean option, `remote_src` has been added to
enable or disable this functionality. When enabled, the module
will retrieve content from the target node and load it as
"user_content". By pre-loading it in the user content the
module will function normally without any major structural
changes or performance impact.

Change-Id: Id9c7e16fb935c2da0b32b7cf53449f68bd1e9c89
Signed-off-by: Kevin Carter <kecarter@redhat.com>
This commit is contained in:
Kevin Carter 2019-07-08 16:21:02 -05:00 committed by Kevin Carter (cloudnull)
parent 28a6032612
commit 73aa099f0a
5 changed files with 95 additions and 11 deletions

View File

@ -38,6 +38,7 @@ import tempfile as tmpfilelib
from ansible.plugins.action import ActionBase from ansible.plugins.action import ActionBase
from ansible.module_utils._text import to_bytes, to_text from ansible.module_utils._text import to_bytes, to_text
from ansible.module_utils.parsing.convert_bool import boolean
from ansible import constants as C from ansible import constants as C
from ansible import errors from ansible import errors
from ansible.parsing.yaml.dumper import AnsibleDumper from ansible.parsing.yaml.dumper import AnsibleDumper
@ -611,17 +612,30 @@ class ActionModule(ActionBase):
file_path = self._loader.get_basedir() file_path = self._loader.get_basedir()
user_source = self._task.args.get('src') user_source = self._task.args.get('src')
# (alextricity25) It's possible that the user could pass in a datatype remote_src = boolean(
# and not always a string. In this case we don't want the datatype self._task.args.get('remote_src', False),
# python representation to be printed out to the file, but rather we strict=False
# want the serialized version. )
_user_content = self._task.args.get('content') if remote_src:
slurpee = self._execute_module(
module_name='slurp',
module_args=dict(src=user_source),
task_vars=task_vars
)
_content = base64.b64decode(slurpee['content'])
_user_content = _content.decode('utf-8')
else:
# (alextricity25) It's possible that the user could pass in a
# datatype and not always a string. In this case we don't want
# the datatype python representation to be printed out to the
# file, but rather we want the serialized version.
_user_content = self._task.args.get('content')
# If the data type of the content input is a dictionary, it's # If the data type of the content input is a dictionary, it's
# converted dumped as json if config_type is 'json'. # converted dumped as json if config_type is 'json'.
if isinstance(_user_content, dict): if isinstance(_user_content, dict):
if self._task.args.get('config_type') == 'json': if self._task.args.get('config_type') == 'json':
_user_content = json.dumps(_user_content) _user_content = json.dumps(_user_content)
user_content = str(_user_content) user_content = str(_user_content)
if not user_source: if not user_source:
@ -671,6 +685,7 @@ class ActionModule(ActionBase):
ignore_none_type = self._task.args.get('ignore_none_type', True) ignore_none_type = self._task.args.get('ignore_none_type', True)
default_section = self._task.args.get('default_section', 'DEFAULT') default_section = self._task.args.get('default_section', 'DEFAULT')
remote_src = self._task.args.get('remote_src', False)
yml_multilines = self._task.args.get('yml_multilines', False) yml_multilines = self._task.args.get('yml_multilines', False)
@ -683,7 +698,8 @@ class ActionModule(ActionBase):
list_extend=list_extend, list_extend=list_extend,
ignore_none_type=ignore_none_type, ignore_none_type=ignore_none_type,
default_section=default_section, default_section=default_section,
yml_multilines=yml_multilines yml_multilines=yml_multilines,
remote_src=remote_src
) )
def run(self, tmp=None, task_vars=None): def run(self, tmp=None, task_vars=None):
@ -838,6 +854,10 @@ class ActionModule(ActionBase):
new_module_args.pop('ignore_none_type', None) new_module_args.pop('ignore_none_type', None)
new_module_args.pop('default_section', None) new_module_args.pop('default_section', None)
new_module_args.pop('yml_multilines', None) new_module_args.pop('yml_multilines', None)
# While this is in the copy module we dont want to use it.
new_module_args.pop('remote_src', None)
# Content from config_template is converted to src # Content from config_template is converted to src
new_module_args.pop('content', None) new_module_args.pop('content', None)

View File

@ -64,6 +64,17 @@ options:
section that will appear at the top of the configuration file. For section that will appear at the top of the configuration file. For
example 'global'. example 'global'.
default: 'DEFAULT' default: 'DEFAULT'
remote_src:
description:
- Influence whether the template needs to be transferred or already is
present remotely.
- If false, it will search the originating machine.
- If true, it will go to the remote/target machine to inject the
template. If the remote source does not exist the module will fail.
choices:
- True
- False
default: false
author: Kevin Carter author: Kevin Carter
""" """

View File

@ -0,0 +1,5 @@
---
features:
- The ability to set `remote_src` has been added to the `config_template`
action plugin. This option will instruct the `config_template` action
plugin to use an already remote file as its source content.

View File

@ -0,0 +1,10 @@
[multistropts]
test = test1
test = test2
test = test3
[remote_src_section]
test = output
[testsection]
test = output

View File

@ -188,6 +188,44 @@
that: that:
- _multistropts_file == _multistropts_expected_file - _multistropts_file == _multistropts_expected_file
# Test remote_src
- name: Template remote source using overrides
config_template:
src: /tmp/test_multistropts.ini
dest: /tmp/test_remote_src_multistropts.ini
remote_src: true
config_overrides:
remote_src_section:
test: output
config_type: ini
- name: Create expected MultiStrOpts file
copy:
src: files/test_remote_src_multistropts.ini.expected
dest: /tmp/test_remote_src_multistropts.ini.expected
- name: Read test_remote_src_multistropts.ini
slurp:
src: /tmp/test_remote_src_multistropts.ini
register: multistropts_file
- name: Read test_remote_src_multistropts.ini.expected
slurp:
src: /tmp/test_remote_src_multistropts.ini.expected
register: multistropts_expected_file
- name: Set content facts
set_fact:
_remote_src_file: "{{ (multistropts_file.content | b64decode).strip() }}"
_remote_src_expected_file: "{{ (multistropts_expected_file.content | b64decode).strip() }}"
- name: Show rendered file
debug:
msg: "multistropts rendered - {{ _remote_src_file }}"
- name: Show expected file
debug:
msg: "multistropts expected - {{ _remote_src_expected_file }}"
- name: Compare files
assert:
that:
- _remote_src_file == _remote_src_expected_file
# Test content attribute with a dictionary input and config_type equal to 'json' # Test content attribute with a dictionary input and config_type equal to 'json'
- name: Template test JSON template with content attribute - name: Template test JSON template with content attribute
config_template: config_template: