From d754a8f02a09cfdf4b0a1cc7ea7189fed797be23 Mon Sep 17 00:00:00 2001 From: SebClem Date: Tue, 13 Jun 2023 17:21:45 +0200 Subject: [PATCH] Init --- .vscode/settings.json | 3 + README.md | 3 + galaxy.yml | 69 +++++ meta/runtime.yml | 52 ++++ plugins/README.md | 31 +++ .../load_haproxy_config.cpython-311.pyc | Bin 0 -> 13208 bytes plugins/action/load_haproxy_config.py | 249 ++++++++++++++++++ plugins/modules/load_haproxy_config.py | 98 +++++++ 8 files changed, 505 insertions(+) create mode 100644 .vscode/settings.json create mode 100644 README.md create mode 100644 galaxy.yml create mode 100644 meta/runtime.yml create mode 100644 plugins/README.md create mode 100644 plugins/action/__pycache__/load_haproxy_config.cpython-311.pyc create mode 100644 plugins/action/load_haproxy_config.py create mode 100644 plugins/modules/load_haproxy_config.py diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..f73b2e0 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "ansible.python.interpreterPath": "/root/.virtualenvs/ansible/bin/python" +} \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..bdb33d5 --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# Ansible Collection - sebclem.haproxy + +Documentation for the collection. diff --git a/galaxy.yml b/galaxy.yml new file mode 100644 index 0000000..ce8153a --- /dev/null +++ b/galaxy.yml @@ -0,0 +1,69 @@ +### REQUIRED +# The namespace of the collection. This can be a company/brand/organization or product namespace under which all +# content lives. May only contain alphanumeric lowercase characters and underscores. Namespaces cannot start with +# underscores or numbers and cannot contain consecutive underscores +namespace: sebclem + +# The name of the collection. Has the same character restrictions as 'namespace' +name: haproxy + +# The version of the collection. Must be compatible with semantic versioning +version: 1.0.0 + +# The path to the Markdown (.md) readme file. This path is relative to the root of the collection +readme: README.md + +# A list of the collection's content authors. Can be just the name or in the format 'Full Name (url) +# @nicks:irc/im.site#channel' +authors: +- your name + + +### OPTIONAL but strongly recommended +# A short summary description of the collection +description: your collection description + +# Either a single license or a list of licenses for content inside of a collection. Ansible Galaxy currently only +# accepts L(SPDX,https://spdx.org/licenses/) licenses. This key is mutually exclusive with 'license_file' +license: +- GPL-2.0-or-later + +# The path to the license file for the collection. This path is relative to the root of the collection. This key is +# mutually exclusive with 'license' +license_file: '' + +# A list of tags you want to associate with the collection for indexing/searching. A tag name has the same character +# requirements as 'namespace' and 'name' +tags: [] + +# Collections that this collection requires to be installed for it to be usable. The key of the dict is the +# collection label 'namespace.name'. The value is a version range +# L(specifiers,https://python-semanticversion.readthedocs.io/en/latest/#requirement-specification). Multiple version +# range specifiers can be set and are separated by ',' +dependencies: {} + +# The URL of the originating SCM repository +repository: http://example.com/repository + +# The URL to any online docs +documentation: http://docs.example.com + +# The URL to the homepage of the collection/project +homepage: http://example.com + +# The URL to the collection issue tracker +issues: http://example.com/issue/tracker + +# A list of file glob-like patterns used to filter any files or directories that should not be included in the build +# artifact. A pattern is matched from the relative path of the file or directory of the collection directory. This +# uses 'fnmatch' to match the files or directories. Some directories and files like 'galaxy.yml', '*.pyc', '*.retry', +# and '.git' are always filtered. Mutually exclusive with 'manifest' +build_ignore: [] + +# A dict controlling use of manifest directives used in building the collection artifact. The key 'directives' is a +# list of MANIFEST.in style +# L(directives,https://packaging.python.org/en/latest/guides/using-manifest-in/#manifest-in-commands). The key +# 'omit_default_directives' is a boolean that controls whether the default directives are used. Mutually exclusive +# with 'build_ignore' +# manifest: null + diff --git a/meta/runtime.yml b/meta/runtime.yml new file mode 100644 index 0000000..20f709e --- /dev/null +++ b/meta/runtime.yml @@ -0,0 +1,52 @@ +--- +# Collections must specify a minimum required ansible version to upload +# to galaxy +# requires_ansible: '>=2.9.10' + +# Content that Ansible needs to load from another location or that has +# been deprecated/removed +# plugin_routing: +# action: +# redirected_plugin_name: +# redirect: ns.col.new_location +# deprecated_plugin_name: +# deprecation: +# removal_version: "4.0.0" +# warning_text: | +# See the porting guide on how to update your playbook to +# use ns.col.another_plugin instead. +# removed_plugin_name: +# tombstone: +# removal_version: "2.0.0" +# warning_text: | +# See the porting guide on how to update your playbook to +# use ns.col.another_plugin instead. +# become: +# cache: +# callback: +# cliconf: +# connection: +# doc_fragments: +# filter: +# httpapi: +# inventory: +# lookup: +# module_utils: +# modules: +# netconf: +# shell: +# strategy: +# terminal: +# test: +# vars: + +# Python import statements that Ansible needs to load from another location +# import_redirection: +# ansible_collections.ns.col.plugins.module_utils.old_location: +# redirect: ansible_collections.ns.col.plugins.module_utils.new_location + +# Groups of actions/modules that take a common set of options +# action_groups: +# group_name: +# - module1 +# - module2 diff --git a/plugins/README.md b/plugins/README.md new file mode 100644 index 0000000..6260634 --- /dev/null +++ b/plugins/README.md @@ -0,0 +1,31 @@ +# Collections Plugins Directory + +This directory can be used to ship various plugins inside an Ansible collection. Each plugin is placed in a folder that +is named after the type of plugin it is in. It can also include the `module_utils` and `modules` directory that +would contain module utils and modules respectively. + +Here is an example directory of the majority of plugins currently supported by Ansible: + +``` +└── plugins + ├── action + ├── become + ├── cache + ├── callback + ├── cliconf + ├── connection + ├── filter + ├── httpapi + ├── inventory + ├── lookup + ├── module_utils + ├── modules + ├── netconf + ├── shell + ├── strategy + ├── terminal + ├── test + └── vars +``` + +A full list of plugin types can be found at [Working With Plugins](https://docs.ansible.com/ansible-core/2.15/plugins/plugins.html). diff --git a/plugins/action/__pycache__/load_haproxy_config.cpython-311.pyc b/plugins/action/__pycache__/load_haproxy_config.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7be75995b2709d013d1f8dd0100efb64560a872e GIT binary patch literal 13208 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 BD~JF9 literal 0 HcmV?d00001 diff --git a/plugins/action/load_haproxy_config.py b/plugins/action/load_haproxy_config.py new file mode 100644 index 0000000..80a91aa --- /dev/null +++ b/plugins/action/load_haproxy_config.py @@ -0,0 +1,249 @@ +# 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.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.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_haproxy_config') + if not "default_domain" in module_args: + raise AnsibleError('\'default_domain\' option is mendatory in load_haproxy_config') + if not "default_dns_provider" in module_args: + raise AnsibleError('\'default_dns_provider\' option is mendatory in load_haproxy_config') + if not "default_dns_target" in module_args: + raise AnsibleError('\'default_dns_target\' option is mendatory in load_haproxy_config') + + self.source_dir = module_args.get('dir') + self.default_domain = module_args.get('default_domain') + self.default_dns_provider = module_args.get('default_dns_provider') + self.default_dns_target = module_args.get('default_dns_target') + self.dir = module_args.get('dir') + self.depth = module_args.get('depth', 0) + + results = { + "domain_maping": [], + "dns_hostnames": dict(), # { provider: [ { hostname:"", domain:"", state: "", target: "" } ] } + "protected_domain": [], + "backend_config": [] + } + 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['domain_maping'] = results['domain_maping'] + updated_results['domain_maping'] + results['protected_domain'] = results['protected_domain'] + updated_results['protected_domain'] + results['backend_config'] = results['backend_config'] + updated_results['backend_config'] + print(updated_results) + for key, value in updated_results['dns_hostnames'].items(): + results['dns_hostnames'][key] = results['dns_hostnames'].get(key, []) + value + + + result = super(ActionModule, self).run(task_vars=task_vars) + + if failed: + result['failed'] = failed + result['message'] = err_msg + scope = dict() + scope['haproxy_config'] = results + results = scope + result['ansible_included_var_files'] = self.included_files + result['ansible_facts'] = 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: + 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 = { + "domain_maping": [], + "dns_hostnames": dict(), # { provider: [ { hostname:"", domain:"", state: "", target: "" } ] } + "protected_domain": [], + "backend_config": [] + } + 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): + 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) + dns_provider = dns.get("provider", self.default_dns_provider) + dns_target = dns.get("target", self.default_dns_provider) + protected = loaded_data.get("protected", False) + additionnal_hostname = loaded_data.get('additionnal_hostname', []) + state = loaded_data.get("state", "present") + if "backend" not in loaded_data: + failed = True + err_msg = ('Could not find "backend" in {0}'.format(to_native(filename))) + continue + backend = loaded_data.get("backend") + + if state == "present": + results['domain_maping'].append('{0}.{1} {2}'.format(main_hostname, domain, backend.get("name"))) + results['backend_config'].append(backend) + if protected: + results['protected_domain'].append('{0}.{1}'.format(main_hostname, domain)) + + if not dns.get("skip", False): + if not dns_provider in results['dns_hostnames']: + results['dns_hostnames'].update({ dns_provider: []}) + + results['dns_hostnames'][dns_provider].append({ + "hostname": main_hostname, + "domain": domain, + "target": dns_target, + "state": state, + }) + + for host in additionnal_hostname: + this_dns = host.get("dns", dns) + this_domain = this_dns.get("domain", domain) + this_dns_provider = this_dns.get("provider", dns_provider) + this_dns_target = this_dns.get("target", dns_target) + this_protected = host.get('protected', protected) + this_state = host.get('state', state) + + if this_state == "present": + results['domain_maping'].append('{0}.{1} {2}'.format(host.get("hostname"), this_domain, backend.get("name"))) + if this_protected: + results['protected_domain'].append('{0}.{1}'.format(host.get("hostname"), this_domain)) + + if not this_dns.get("skip", dns.get("skip", False)): + if not this_dns_provider in results['dns_hostnames']: + results['dns_hostnames'].update({ this_dns_provider: []}) + + results['dns_hostnames'][this_dns_provider].append({ + "hostname": host.get("hostname"), + "domain": this_domain, + "target": this_dns_target, + "state": this_state + }) + return failed, err_msg, results \ No newline at end of file diff --git a/plugins/modules/load_haproxy_config.py b/plugins/modules/load_haproxy_config.py new file mode 100644 index 0000000..fce9821 --- /dev/null +++ b/plugins/modules/load_haproxy_config.py @@ -0,0 +1,98 @@ +# -*- coding: utf-8 -*- + +# 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 + + +DOCUMENTATION = r''' +--- +author: Sebastien Clement (@sebclem) +module: load_haproxy_config +short_description: Load variables from files for haproxy config +description: [] +version_added: "2.7" +options: + dir: + description: + - The directory name from which the variables should be loaded. + - If the path is relative and the task is inside a role, it will look inside the role's vars/ subdirectory. + - If the path is relative and not inside a role, it will be parsed relative to the playbook. + type: path + version_added: "2.1" + required: true + depth: + description: + - This module will, by default, recursively go through each sub directory and load up the + variables. By explicitly setting the depth, this module will only go as deep as the depth. + type: int + default: 0 + version_added: "2.2" + default_domain: + description: + - Default root domain + type: str + version_added: "2.2" + required: true + default_dns_provider: + description: + - Default dns provider + type: str + version_added: "2.2" + required: true + default_dns_target: + description: + - Default dns target + type: str + version_added: "2.2" + required: true + +attributes: + action: + details: While the action plugin does do some of the work it relies on the core engine to actually create the variables, that part cannot be overridden + support: partial + bypass_host_loop: + support: none + bypass_task_loop: + support: none + check_mode: + support: full + delegation: + details: + - while variable assignment can be delegated to a different host the execution context is always the current inventory_hostname + - connection variables, if set at all, would reflect the host it would target, even if we are not connecting at all in this case + support: partial + diff_mode: + support: none + core: + details: While parts of this action are implemented in core, other parts are still available as normal plugins and can be partially overridden + support: partial +seealso: +- module: ansible.builtin.set_fact +- ref: playbooks_delegation + description: More information related to task delegation. +''' + +EXAMPLES = r''' +- name: Test + sebclem.haproxy.load_haproxy_config: + dir: vars/ + default_domain: default_domain + default_dns_provider: default_dns_provider + default_dns_target: default_dns_target +''' + +RETURN = r''' +ansible_facts: + description: Variables that were included and their values + returned: success + type: dict + sample: {'variable': 'value'} +ansible_included_var_files: + description: A list of files that were successfully included + returned: success + type: list + sample: [ /path/to/file.json, /path/to/file.yaml ] + version_added: '2.4' +''' \ No newline at end of file