# Copyright: (c) 2016, Allen Sanabria # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) from __future__ import (absolute_import, division, print_function) __metaclass__ = type from os import path, walk import re import ansible.constants as C from ansible.errors import AnsibleError from ansible.module_utils.six import string_types from ansible.module_utils.common.text.converters import to_native, to_text from ansible.plugins.action import ActionBase from ansible.template import Templar from ansible.utils.vars import combine_vars from pathlib import Path class ActionModule(ActionBase): TRANSFERS_FILES = False _requires_connection = False def run(self, tmp=None, task_vars=None): """ Load yml files recursively from a directory. """ del tmp # tmp no longer has any effect if task_vars is None: task_vars = dict() self.task_var = task_vars self.show_content = True self.included_files = [] # Validate arguments dirs = 0 module_args = self._task.args.copy() if not "dir" in module_args: raise AnsibleError('\'dir\' option is mendatory in load_hasites_config') if not "default_domain" in module_args: raise AnsibleError('\'default_domain\' option is mendatory in load_hasites_config') if not "target" in module_args: raise AnsibleError('\'target\' option is mendatory in load_hasites_config') self.source_dir = module_args.get('dir') self.default_domain = module_args.get('default_domain') self.dir = module_args.get('dir') self.depth = module_args.get('depth', 0) results = { "sites": [] } failed = False self._set_root_dir() if not path.exists(self.source_dir): failed = True err_msg = ('{0} directory does not exist'.format(to_native(self.source_dir))) elif not path.isdir(self.source_dir): failed = True err_msg = ('{0} is not a directory'.format(to_native(self.source_dir))) else: for root_dir, filenames in self._traverse_dir_depth(): failed, err_msg, updated_results = (self._load_files_in_dir(root_dir, filenames)) if failed: break results['sites'] = results['sites'] + updated_results['sites'] result = { "relay_conf": { "sites": results["sites"], "target": module_args['target'] } } result = super(ActionModule, self).run(task_vars=task_vars) if failed: result['failed'] = failed result['message'] = err_msg scope = dict() scope['caddy_config'] = results results = scope result['ansible_included_var_files'] = self.included_files if not "target_var" in module_args: result['ansible_facts'] = results else: result['ansible_facts'] = { module_args['target_var']: results } result['_ansible_no_log'] = not self.show_content return result def _set_root_dir(self): if self._task._role: if self.source_dir.split('/')[0] == 'vars': path_to_use = ( path.join(self._task._role._role_path, self.source_dir) ) if path.exists(path_to_use): self.source_dir = path_to_use else: path_to_use = ( path.join( self._task._role._role_path, 'vars', self.source_dir ) ) self.source_dir = path_to_use else: if hasattr(self._task._ds, '_data_source'): current_dir = ( "/".join(self._task._ds._data_source.split('/')[:-1]) ) self.source_dir = path.join(current_dir, self.source_dir) def _log_walk(self, error): self._display.vvv('Issue with walking through "%s": %s' % (to_native(error.filename), to_native(error))) def _traverse_dir_depth(self): """ Recursively iterate over a directory and sort the files in alphabetical order. Do not iterate pass the set depth. The default depth is unlimited. """ current_depth = 0 sorted_walk = list(walk(self.source_dir, onerror=self._log_walk, followlinks=True)) sorted_walk.sort(key=lambda x: x[0]) for current_root, current_dir, current_files in sorted_walk: current_depth += 1 if current_depth <= self.depth or self.depth == 0: current_files.sort() yield (current_root, current_files) else: break def _load_files(self, filename): """ Loads a file and converts the output into a valid Python dict. Args: filename (str): The source file. Returns: Tuple (bool, str, dict) """ results = dict() failed = False err_msg = '' b_data, show_content = self._loader._get_file_contents(filename) data = to_text(b_data, errors='surrogate_or_strict') self.show_content = show_content data = self._loader.load(data, file_name=filename, show_content=show_content) if not data: data = dict() if not isinstance(data, dict): failed = True err_msg = ('{0} must be stored as a dictionary/hash'.format(to_native(filename))) else: # Apply Ansible templating to the data templar = Templar(loader=self._loader, variables=self.task_var) data = templar.template(data) self.included_files.append(filename) results.update(data) return failed, err_msg, results def _load_files_in_dir(self, root_dir, var_files): """ Load the found yml files and update/overwrite the dictionary. Args: root_dir (str): The base directory of the list of files that is being passed. var_files: (list): List of files to iterate over and load into a dictionary. Returns: Tuple (bool, str, dict) """ results = { "sites": [] } failed = False err_msg = '' for filename in var_files: stop_iter = False # Never include main.yml from a role, as that is the default included by the role if self._task._role: if path.join(self._task._role._role_path, filename) == path.join(root_dir, 'vars', 'main.yml'): stop_iter = True continue filepath = path.join(root_dir, filename) if not stop_iter and not failed: if path.exists(filepath): loaded_data = {} failed, err_msg, loaded_data = self._load_files(filepath) if not failed: main_hostname = Path(filepath).stem dns = loaded_data.get("dns", dict()) domain = dns.get("domain", self.default_domain) additionnal_hostname = loaded_data.get('additionnal_hostname', []) state = loaded_data.get("state", "present") if "upstream" not in loaded_data: failed = True err_msg = ('Could not find "upstream" in {0}'.format(to_native(filename))) continue if state == "present": results['sites'].append('{0}.{1}'.format(main_hostname, domain)) for host in additionnal_hostname: this_dns = host.get("dns", dns) this_domain = this_dns.get("domain", domain) this_state = host.get('state', state) full_domain = '{0}.{1}'.format(host.get("hostname"), this_domain) if host.get("hostname") else this_domain this_upstream_config = host.get("upstream_config") if this_state == "present": results['sites'].append(full_domain) return failed, err_msg, results