mirror of https://github.com/ceph/ceph-ansible.git
241 lines
7.8 KiB
Python
241 lines
7.8 KiB
Python
|
# Copyright 2015, Rackspace US, Inc.
|
||
|
#
|
||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||
|
# you may not use this file except in compliance with the License.
|
||
|
# You may obtain a copy of the License at
|
||
|
#
|
||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||
|
#
|
||
|
# Unless required by applicable law or agreed to in writing, software
|
||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||
|
# See the License for the specific language governing permissions and
|
||
|
# limitations under the License.
|
||
|
|
||
|
|
||
|
import ConfigParser
|
||
|
import io
|
||
|
import json
|
||
|
import os
|
||
|
import yaml
|
||
|
|
||
|
from ansible import errors
|
||
|
from ansible.runner.return_data import ReturnData
|
||
|
from ansible import utils
|
||
|
from ansible.utils import template
|
||
|
|
||
|
|
||
|
CONFIG_TYPES = {
|
||
|
'ini': 'return_config_overrides_ini',
|
||
|
'json': 'return_config_overrides_json',
|
||
|
'yaml': 'return_config_overrides_yaml'
|
||
|
}
|
||
|
|
||
|
|
||
|
class ActionModule(object):
|
||
|
TRANSFERS_FILES = True
|
||
|
|
||
|
def __init__(self, runner):
|
||
|
self.runner = runner
|
||
|
|
||
|
def grab_options(self, complex_args, module_args):
|
||
|
"""Grab passed options from Ansible complex and module args.
|
||
|
|
||
|
:param complex_args: ``dict``
|
||
|
:param module_args: ``dict``
|
||
|
:returns: ``dict``
|
||
|
"""
|
||
|
options = dict()
|
||
|
if complex_args:
|
||
|
options.update(complex_args)
|
||
|
|
||
|
options.update(utils.parse_kv(module_args))
|
||
|
return options
|
||
|
|
||
|
@staticmethod
|
||
|
def return_config_overrides_ini(config_overrides, resultant):
|
||
|
"""Returns string value from a modified config file.
|
||
|
|
||
|
:param config_overrides: ``dict``
|
||
|
:param resultant: ``str`` || ``unicode``
|
||
|
:returns: ``str``
|
||
|
"""
|
||
|
config = ConfigParser.RawConfigParser(allow_no_value=True)
|
||
|
config_object = io.BytesIO(resultant.encode('utf-8'))
|
||
|
config.readfp(config_object)
|
||
|
for section, items in config_overrides.items():
|
||
|
# If the items value is not a dictionary it is assumed that the
|
||
|
# value is a default item for this config type.
|
||
|
if not isinstance(items, dict):
|
||
|
config.set('DEFAULT', section, str(items))
|
||
|
else:
|
||
|
# Attempt to add a section to the config file passing if
|
||
|
# an error is raised that is related to the section
|
||
|
# already existing.
|
||
|
try:
|
||
|
config.add_section(section)
|
||
|
except (ConfigParser.DuplicateSectionError, ValueError):
|
||
|
pass
|
||
|
for key, value in items.items():
|
||
|
config.set(section, key, str(value))
|
||
|
else:
|
||
|
config_object.close()
|
||
|
|
||
|
resultant_bytesio = io.BytesIO()
|
||
|
try:
|
||
|
config.write(resultant_bytesio)
|
||
|
return resultant_bytesio.getvalue()
|
||
|
finally:
|
||
|
resultant_bytesio.close()
|
||
|
|
||
|
def return_config_overrides_json(self, config_overrides, resultant):
|
||
|
"""Returns config json
|
||
|
|
||
|
Its important to note that file ordering will not be preserved as the
|
||
|
information within the json file will be sorted by keys.
|
||
|
|
||
|
:param config_overrides: ``dict``
|
||
|
:param resultant: ``str`` || ``unicode``
|
||
|
:returns: ``str``
|
||
|
"""
|
||
|
original_resultant = json.loads(resultant)
|
||
|
merged_resultant = self._merge_dict(
|
||
|
base_items=original_resultant,
|
||
|
new_items=config_overrides
|
||
|
)
|
||
|
return json.dumps(
|
||
|
merged_resultant,
|
||
|
indent=4,
|
||
|
sort_keys=True
|
||
|
)
|
||
|
|
||
|
def return_config_overrides_yaml(self, config_overrides, resultant):
|
||
|
"""Return config yaml.
|
||
|
|
||
|
:param config_overrides: ``dict``
|
||
|
:param resultant: ``str`` || ``unicode``
|
||
|
:returns: ``str``
|
||
|
"""
|
||
|
original_resultant = yaml.safe_load(resultant)
|
||
|
merged_resultant = self._merge_dict(
|
||
|
base_items=original_resultant,
|
||
|
new_items=config_overrides
|
||
|
)
|
||
|
return yaml.safe_dump(
|
||
|
merged_resultant,
|
||
|
default_flow_style=False,
|
||
|
width=1000,
|
||
|
)
|
||
|
|
||
|
def _merge_dict(self, base_items, new_items):
|
||
|
"""Recursively merge new_items into base_items.
|
||
|
|
||
|
:param base_items: ``dict``
|
||
|
:param new_items: ``dict``
|
||
|
:returns: ``dict``
|
||
|
"""
|
||
|
for key, value in new_items.iteritems():
|
||
|
if isinstance(value, dict):
|
||
|
base_items[key] = self._merge_dict(
|
||
|
base_items.get(key, {}),
|
||
|
value
|
||
|
)
|
||
|
elif isinstance(value, list):
|
||
|
if key in base_items and isinstance(base_items[key], list):
|
||
|
base_items[key].extend(value)
|
||
|
else:
|
||
|
base_items[key] = value
|
||
|
else:
|
||
|
base_items[key] = new_items[key]
|
||
|
return base_items
|
||
|
|
||
|
def run(self, conn, tmp, module_name, module_args, inject,
|
||
|
complex_args=None, **kwargs):
|
||
|
"""Run the method"""
|
||
|
if not self.runner.is_playbook:
|
||
|
raise errors.AnsibleError(
|
||
|
'FAILED: `config_templates` are only available in playbooks'
|
||
|
)
|
||
|
|
||
|
options = self.grab_options(complex_args, module_args)
|
||
|
try:
|
||
|
source = options['src']
|
||
|
dest = options['dest']
|
||
|
|
||
|
config_overrides = options.get('config_overrides', dict())
|
||
|
config_type = options['config_type']
|
||
|
assert config_type.lower() in ['ini', 'json', 'yaml']
|
||
|
except KeyError as exp:
|
||
|
result = dict(failed=True, msg=exp)
|
||
|
return ReturnData(conn=conn, comm_ok=False, result=result)
|
||
|
|
||
|
source_template = template.template(
|
||
|
self.runner.basedir,
|
||
|
source,
|
||
|
inject
|
||
|
)
|
||
|
|
||
|
if '_original_file' in inject:
|
||
|
source_file = utils.path_dwim_relative(
|
||
|
inject['_original_file'],
|
||
|
'templates',
|
||
|
source_template,
|
||
|
self.runner.basedir
|
||
|
)
|
||
|
else:
|
||
|
source_file = utils.path_dwim(self.runner.basedir, source_template)
|
||
|
|
||
|
# Open the template file and return the data as a string. This is
|
||
|
# being done here so that the file can be a vault encrypted file.
|
||
|
resultant = template.template_from_file(
|
||
|
self.runner.basedir,
|
||
|
source_file,
|
||
|
inject,
|
||
|
vault_password=self.runner.vault_pass
|
||
|
)
|
||
|
|
||
|
if config_overrides:
|
||
|
type_merger = getattr(self, CONFIG_TYPES.get(config_type))
|
||
|
resultant = type_merger(
|
||
|
config_overrides=config_overrides,
|
||
|
resultant=resultant
|
||
|
)
|
||
|
|
||
|
# Retemplate the resultant object as it may have new data within it
|
||
|
# as provided by an override variable.
|
||
|
template.template_from_string(
|
||
|
basedir=self.runner.basedir,
|
||
|
data=resultant,
|
||
|
vars=inject,
|
||
|
fail_on_undefined=True
|
||
|
)
|
||
|
|
||
|
# Access to protected method is unavoidable in Ansible 1.x.
|
||
|
new_module_args = dict(
|
||
|
src=self.runner._transfer_str(conn, tmp, 'source', resultant),
|
||
|
dest=dest,
|
||
|
original_basename=os.path.basename(source),
|
||
|
follow=True,
|
||
|
)
|
||
|
|
||
|
module_args_tmp = utils.merge_module_args(
|
||
|
module_args,
|
||
|
new_module_args
|
||
|
)
|
||
|
|
||
|
# Remove data types that are not available to the copy module
|
||
|
complex_args.pop('config_overrides')
|
||
|
complex_args.pop('config_type')
|
||
|
|
||
|
# Return the copy module status. Access to protected method is
|
||
|
# unavoidable in Ansible 1.x.
|
||
|
return self.runner._execute_module(
|
||
|
conn,
|
||
|
tmp,
|
||
|
'copy',
|
||
|
module_args_tmp,
|
||
|
inject=inject,
|
||
|
complex_args=complex_args
|
||
|
)
|
||
|
|