diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-13 12:04:41 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-13 12:04:41 +0000 |
commit | 975f66f2eebe9dadba04f275774d4ab83f74cf25 (patch) | |
tree | 89bd26a93aaae6a25749145b7e4bca4a1e75b2be /ansible_collections/ngine_io/exoscale/plugins | |
parent | Initial commit. (diff) | |
download | ansible-975f66f2eebe9dadba04f275774d4ab83f74cf25.tar.xz ansible-975f66f2eebe9dadba04f275774d4ab83f74cf25.zip |
Adding upstream version 7.7.0+dfsg.upstream/7.7.0+dfsg
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'ansible_collections/ngine_io/exoscale/plugins')
6 files changed, 737 insertions, 0 deletions
diff --git a/ansible_collections/ngine_io/exoscale/plugins/doc_fragments/__init__.py b/ansible_collections/ngine_io/exoscale/plugins/doc_fragments/__init__.py new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/ansible_collections/ngine_io/exoscale/plugins/doc_fragments/__init__.py diff --git a/ansible_collections/ngine_io/exoscale/plugins/doc_fragments/exoscale.py b/ansible_collections/ngine_io/exoscale/plugins/doc_fragments/exoscale.py new file mode 100644 index 000000000..52ea2cd49 --- /dev/null +++ b/ansible_collections/ngine_io/exoscale/plugins/doc_fragments/exoscale.py @@ -0,0 +1,56 @@ +# -*- coding: utf-8 -*- + +# Copyright: (c) 2017, René Moser <mail@renemoser.net> +# 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 + + +class ModuleDocFragment(object): + + # Standard exoscale documentation fragment + DOCUMENTATION = r''' +options: + api_key: + description: + - API key of the Exoscale DNS API. + - The ENV variable C(CLOUDSTACK_KEY) is used as default, when defined. + type: str + api_secret: + description: + - Secret key of the Exoscale DNS API. + - The ENV variable C(CLOUDSTACK_SECRET) is used as default, when defined. + type: str + api_timeout: + description: + - HTTP timeout to Exoscale DNS API. + - The ENV variable C(CLOUDSTACK_TIMEOUT) is used as default, when defined. + type: int + default: 10 + api_region: + description: + - Name of the ini section in the C(cloustack.ini) file. + - The ENV variable C(CLOUDSTACK_REGION) is used as default, when defined. + type: str + default: cloudstack + validate_certs: + description: + - Validate SSL certs of the Exoscale DNS API. + type: bool + default: yes +requirements: + - python >= 2.6 +notes: + - As Exoscale DNS uses the same API key and secret for all services, we reuse the config used for Exscale Compute based on CloudStack. + The config is read from several locations, in the following order. + The C(CLOUDSTACK_KEY), C(CLOUDSTACK_SECRET) environment variables. + A C(CLOUDSTACK_CONFIG) environment variable pointing to an C(.ini) file, + A C(cloudstack.ini) file in the current working directory. + A C(.cloudstack.ini) file in the users home directory. + Optionally multiple credentials and endpoints can be specified using ini sections in C(cloudstack.ini). + Use the argument C(api_region) to select the section name, default section is C(cloudstack). + - This module does not support multiple A records and will complain properly if you try. + - More information Exoscale DNS can be found on https://community.exoscale.ch/documentation/dns/. + - This module supports check mode and diff. +''' diff --git a/ansible_collections/ngine_io/exoscale/plugins/module_utils/exoscale.py b/ansible_collections/ngine_io/exoscale/plugins/module_utils/exoscale.py new file mode 100644 index 000000000..44933b1b0 --- /dev/null +++ b/ansible_collections/ngine_io/exoscale/plugins/module_utils/exoscale.py @@ -0,0 +1,138 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2016, René Moser <mail@renemoser.net> +# 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 + +import os + +from ansible.module_utils.six.moves import configparser +from ansible.module_utils.six import integer_types, string_types +from ansible.module_utils._text import to_native, to_text +from ansible.module_utils.urls import fetch_url + +EXO_DNS_BASEURL = "https://api.exoscale.ch/dns/v1" + + +def exo_dns_argument_spec(): + return dict( + api_key=dict(type='str', default=os.environ.get('CLOUDSTACK_KEY'), no_log=True), + api_secret=dict(type='str', default=os.environ.get('CLOUDSTACK_SECRET'), no_log=True), + api_timeout=dict(type='int', default=os.environ.get('CLOUDSTACK_TIMEOUT') or 10), + api_region=dict(type='str', default=os.environ.get('CLOUDSTACK_REGION') or 'cloudstack'), + validate_certs=dict(default=True, type='bool'), + ) + + +def exo_dns_required_together(): + return [['api_key', 'api_secret']] + + +class ExoDns(object): + + def __init__(self, module): + self.module = module + + self.api_key = self.module.params.get('api_key') + self.api_secret = self.module.params.get('api_secret') + if not (self.api_key and self.api_secret): + try: + region = self.module.params.get('api_region') + config = self.read_config(ini_group=region) + self.api_key = config['key'] + self.api_secret = config['secret'] + except Exception as e: + self.module.fail_json(msg="Error while processing config: %s" % to_native(e)) + + self.headers = { + 'X-DNS-Token': "%s:%s" % (self.api_key, self.api_secret), + 'Content-Type': 'application/json', + 'Accept': 'application/json', + } + self.result = { + 'changed': False, + 'diff': { + 'before': {}, + 'after': {}, + } + } + + def read_config(self, ini_group=None): + if not ini_group: + ini_group = os.environ.get('CLOUDSTACK_REGION', 'cloudstack') + + keys = ['key', 'secret'] + env_conf = {} + for key in keys: + if 'CLOUDSTACK_%s' % key.upper() not in os.environ: + break + else: + env_conf[key] = os.environ['CLOUDSTACK_%s' % key.upper()] + else: + return env_conf + + # Config file: $PWD/cloudstack.ini or $HOME/.cloudstack.ini + # Last read wins in configparser + paths = ( + os.path.join(os.path.expanduser('~'), '.cloudstack.ini'), + os.path.join(os.getcwd(), 'cloudstack.ini'), + ) + # Look at CLOUDSTACK_CONFIG first if present + if 'CLOUDSTACK_CONFIG' in os.environ: + paths += (os.path.expanduser(os.environ['CLOUDSTACK_CONFIG']),) + if not any([os.path.exists(c) for c in paths]): + self.module.fail_json(msg="Config file not found. Tried : %s" % ", ".join(paths)) + + conf = configparser.ConfigParser() + conf.read(paths) + return dict(conf.items(ini_group)) + + def api_query(self, resource="/domains", method="GET", data=None): + url = EXO_DNS_BASEURL + resource + if data: + data = self.module.jsonify(data) + + response, info = fetch_url( + module=self.module, + url=url, + data=data, + method=method, + headers=self.headers, + timeout=self.module.params.get('api_timeout'), + ) + + if info['status'] not in (200, 201, 204): + self.module.fail_json(msg="%s returned %s, with body: %s" % (url, info['status'], info['msg'])) + + try: + return self.module.from_json(to_text(response.read())) + + except Exception as e: + self.module.fail_json(msg="Could not process response into json: %s" % to_native(e)) + + def has_changed(self, want_dict, current_dict, only_keys=None): + changed = False + for key, value in want_dict.items(): + # Optionally limit by a list of keys + if only_keys and key not in only_keys: + continue + # Skip None values + if value is None: + continue + if key in current_dict: + if isinstance(current_dict[key], integer_types): + if value != current_dict[key]: + self.result['diff']['before'][key] = current_dict[key] + self.result['diff']['after'][key] = value + changed = True + elif isinstance(current_dict[key], string_types): + if value.lower() != current_dict[key].lower(): + self.result['diff']['before'][key] = current_dict[key] + self.result['diff']['after'][key] = value + changed = True + else: + self.module.fail_json(msg="Unable to determine comparison for key %s" % key) + else: + self.result['diff']['after'][key] = value + changed = True + return changed diff --git a/ansible_collections/ngine_io/exoscale/plugins/modules/__init__.py b/ansible_collections/ngine_io/exoscale/plugins/modules/__init__.py new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/ansible_collections/ngine_io/exoscale/plugins/modules/__init__.py diff --git a/ansible_collections/ngine_io/exoscale/plugins/modules/exo_dns_domain.py b/ansible_collections/ngine_io/exoscale/plugins/modules/exo_dns_domain.py new file mode 100644 index 000000000..334c5c02b --- /dev/null +++ b/ansible_collections/ngine_io/exoscale/plugins/modules/exo_dns_domain.py @@ -0,0 +1,204 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# Copyright (c) 2016, René Moser <mail@renemoser.net> +# 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 = ''' +--- +module: exo_dns_domain +short_description: Manages domain records on Exoscale DNS API. +description: + - Create and remove domain records. +author: "René Moser (@resmo)" +version_added: "0.1.0" +options: + name: + description: + - Name of the record. + required: true + type: str + state: + description: + - State of the resource. + default: present + choices: [ present, absent ] + type: str +extends_documentation_fragment: ngine_io.exoscale.exoscale +''' + +EXAMPLES = ''' +- name: Create a domain + exo_dns_domain: + name: example.com + +- name: Remove a domain + exo_dns_domain: + name: example.com + state: absent +''' + +RETURN = ''' +--- +exo_dns_domain: + description: API domain results + returned: success + type: complex + contains: + account_id: + description: Your account ID + returned: success + type: int + sample: 34569 + auto_renew: + description: Whether domain is auto renewed or not + returned: success + type: bool + sample: false + created_at: + description: When the domain was created + returned: success + type: str + sample: "2016-08-12T15:24:23.989Z" + expires_on: + description: When the domain expires + returned: success + type: str + sample: "2016-08-12T15:24:23.989Z" + id: + description: ID of the domain + returned: success + type: int + sample: "2016-08-12T15:24:23.989Z" + lockable: + description: Whether the domain is lockable or not + returned: success + type: bool + sample: true + name: + description: Domain name + returned: success + type: str + sample: example.com + record_count: + description: Number of records related to this domain + returned: success + type: int + sample: 5 + registrant_id: + description: ID of the registrant + returned: success + type: int + sample: null + service_count: + description: Number of services + returned: success + type: int + sample: 0 + state: + description: State of the domain + returned: success + type: str + sample: "hosted" + token: + description: Token + returned: success + type: str + sample: "r4NzTRp6opIeFKfaFYvOd6MlhGyD07jl" + unicode_name: + description: Domain name as unicode + returned: success + type: str + sample: "example.com" + updated_at: + description: When the domain was updated last. + returned: success + type: str + sample: "2016-08-12T15:24:23.989Z" + user_id: + description: ID of the user + returned: success + type: int + sample: null + whois_protected: + description: Whether the whois is protected or not + returned: success + type: bool + sample: false +''' + +from ansible.module_utils.basic import AnsibleModule +from ..module_utils.exoscale import ExoDns, exo_dns_argument_spec, exo_dns_required_together + + +class ExoDnsDomain(ExoDns): + + def __init__(self, module): + super(ExoDnsDomain, self).__init__(module) + self.name = self.module.params.get('name').lower() + + def get_domain(self): + domains = self.api_query("/domains", "GET") + for z in domains: + if z['domain']['name'].lower() == self.name: + return z + return None + + def present_domain(self): + domain = self.get_domain() + data = { + 'domain': { + 'name': self.name, + } + } + if not domain: + self.result['diff']['after'] = data['domain'] + self.result['changed'] = True + if not self.module.check_mode: + domain = self.api_query("/domains", "POST", data) + return domain + + def absent_domain(self): + domain = self.get_domain() + if domain: + self.result['diff']['before'] = domain + self.result['changed'] = True + if not self.module.check_mode: + self.api_query("/domains/%s" % domain['domain']['name'], "DELETE") + return domain + + def get_result(self, resource): + if resource: + self.result['exo_dns_domain'] = resource['domain'] + return self.result + + +def main(): + argument_spec = exo_dns_argument_spec() + argument_spec.update(dict( + name=dict(type='str', required=True), + state=dict(type='str', choices=['present', 'absent'], default='present'), + )) + + module = AnsibleModule( + argument_spec=argument_spec, + required_together=exo_dns_required_together(), + supports_check_mode=True + ) + + exo_dns_domain = ExoDnsDomain(module) + if module.params.get('state') == "present": + resource = exo_dns_domain.present_domain() + else: + resource = exo_dns_domain.absent_domain() + result = exo_dns_domain.get_result(resource) + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/ansible_collections/ngine_io/exoscale/plugins/modules/exo_dns_record.py b/ansible_collections/ngine_io/exoscale/plugins/modules/exo_dns_record.py new file mode 100644 index 000000000..8da3491eb --- /dev/null +++ b/ansible_collections/ngine_io/exoscale/plugins/modules/exo_dns_record.py @@ -0,0 +1,339 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# Copyright (c) 2016, René Moser <mail@renemoser.net> +# 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 = ''' +--- +module: exo_dns_record +short_description: Manages DNS records on Exoscale DNS. +description: + - Create, update and delete records. +author: "René Moser (@resmo)" +version_added: "0.1.0" +options: + name: + description: + - Name of the record. + default: "" + type: str + domain: + description: + - Domain the record is related to. + required: true + type: str + record_type: + description: + - Type of the record. + default: A + choices: [ A, ALIAS, CNAME, MX, SPF, URL, TXT, NS, SRV, NAPTR, PTR, AAAA, SSHFP, HINFO, POOL ] + aliases: [ rtype, type ] + type: str + content: + description: + - Content of the record. + - Required if C(state=present) or C(multiple=yes). + aliases: [ value, address ] + type: str + ttl: + description: + - TTL of the record in seconds. + default: 3600 + type: int + prio: + description: + - Priority of the record. + aliases: [ priority ] + type: int + multiple: + description: + - Whether there are more than one records with similar I(name) and I(record_type). + - Only allowed for a few record types, e.g. C(record_type=A), C(record_type=NS) or C(record_type=MX). + - I(content) will not be updated, instead it is used as a key to find existing records. + type: bool + default: no + state: + description: + - State of the record. + default: present + choices: [ present, absent ] + type: str +extends_documentation_fragment: ngine_io.exoscale.exoscale +''' + +EXAMPLES = ''' +- name: Create or update an A record + ngine_io.exoscale.exo_dns_record: + name: web-vm-1 + domain: example.com + content: 1.2.3.4 + +- name: Update an existing A record with a new IP + ngine_io.exoscale.exo_dns_record: + name: web-vm-1 + domain: example.com + content: 1.2.3.5 + +- name: Create another A record with same name + ngine_io.exoscale.exo_dns_record: + name: web-vm-1 + domain: example.com + content: 1.2.3.6 + multiple: yes + +- name: Create or update a CNAME record + ngine_io.exoscale.exo_dns_record: + name: www + domain: example.com + record_type: CNAME + content: web-vm-1 + +- name: Create another MX record + ngine_io.exoscale.exo_dns_record: + domain: example.com + record_type: MX + content: mx1.example.com + prio: 10 + multiple: yes + +- name: Delete one MX record out of multiple + ngine_io.exoscale.exo_dns_record: + domain: example.com + record_type: MX + content: mx1.example.com + multiple: yes + state: absent + +- name: Remove a single A record + ngine_io.exoscale.exo_dns_record: + name: www + domain: example.com + state: absent +''' + +RETURN = ''' +--- +exo_dns_record: + description: API record results + returned: success + type: complex + contains: + content: + description: value of the record + returned: success + type: str + sample: 1.2.3.4 + created_at: + description: When the record was created + returned: success + type: str + sample: "2016-08-12T15:24:23.989Z" + domain: + description: Name of the domain + returned: success + type: str + sample: example.com + domain_id: + description: ID of the domain + returned: success + type: int + sample: 254324 + id: + description: ID of the record + returned: success + type: int + sample: 254324 + name: + description: name of the record + returned: success + type: str + sample: www + parent_id: + description: ID of the parent + returned: success + type: int + sample: null + prio: + description: Priority of the record + returned: success + type: int + sample: 10 + record_type: + description: Priority of the record + returned: success + type: str + sample: A + system_record: + description: Whether the record is a system record or not + returned: success + type: bool + sample: false + ttl: + description: Time to live of the record + returned: success + type: int + sample: 3600 + updated_at: + description: When the record was updated + returned: success + type: str + sample: "2016-08-12T15:24:23.989Z" +''' + +from ansible.module_utils.basic import AnsibleModule +from ..module_utils.exoscale import ExoDns, exo_dns_argument_spec, exo_dns_required_together + + +EXO_RECORD_TYPES = [ + 'A', + 'ALIAS', + 'CNAME', + 'MX', + 'SPF', + 'URL', + 'TXT', + 'NS', + 'SRV', + 'NAPTR', + 'PTR', + 'AAAA', + 'SSHFP', + 'HINFO', + 'POOL' +] + + +class ExoDnsRecord(ExoDns): + + def __init__(self, module): + super(ExoDnsRecord, self).__init__(module) + + self.domain = self.module.params.get('domain').lower() + self.name = self.module.params.get('name').lower() + if self.name == self.domain: + self.name = "" + + self.multiple = self.module.params.get('multiple') + self.record_type = self.module.params.get('record_type') + self.content = self.module.params.get('content') + + def _create_record(self, record): + self.result['changed'] = True + data = { + 'record': { + 'name': self.name, + 'record_type': self.record_type, + 'content': self.content, + 'ttl': self.module.params.get('ttl'), + 'prio': self.module.params.get('prio'), + } + } + self.result['diff']['after'] = data['record'] + if not self.module.check_mode: + record = self.api_query("/domains/%s/records" % self.domain, "POST", data) + return record + + def _update_record(self, record): + data = { + 'record': { + 'name': self.name, + 'content': self.content, + 'ttl': self.module.params.get('ttl'), + 'prio': self.module.params.get('prio'), + } + } + if self.has_changed(data['record'], record['record']): + self.result['changed'] = True + if not self.module.check_mode: + record = self.api_query("/domains/%s/records/%s" % (self.domain, record['record']['id']), "PUT", data) + return record + + def get_record(self): + domain = self.module.params.get('domain') + records = self.api_query("/domains/%s/records" % domain, "GET") + + result = {} + for r in records: + + if r['record']['record_type'] != self.record_type: + continue + + r_name = r['record']['name'].lower() + r_content = r['record']['content'] + + if r_name == self.name: + if not self.multiple: + if result: + self.module.fail_json(msg="More than one record with record_type=%s and name=%s params. " + "Use multiple=yes for more than one record." % (self.record_type, self.name)) + else: + result = r + elif r_content == self.content: + return r + + return result + + def present_record(self): + record = self.get_record() + if not record: + record = self._create_record(record) + else: + record = self._update_record(record) + return record + + def absent_record(self): + record = self.get_record() + if record: + self.result['diff']['before'] = record + self.result['changed'] = True + if not self.module.check_mode: + self.api_query("/domains/%s/records/%s" % (self.domain, record['record']['id']), "DELETE") + return record + + def get_result(self, resource): + if resource: + self.result['exo_dns_record'] = resource['record'] + self.result['exo_dns_record']['domain'] = self.domain + return self.result + + +def main(): + argument_spec = exo_dns_argument_spec() + argument_spec.update(dict( + name=dict(type='str', default=''), + record_type=dict(type='str', choices=EXO_RECORD_TYPES, aliases=['rtype', 'type'], default='A'), + content=dict(type='str', aliases=['value', 'address']), + multiple=(dict(type='bool', default=False)), + ttl=dict(type='int', default=3600), + prio=dict(type='int', aliases=['priority']), + domain=dict(type='str', required=True), + state=dict(type='str', choices=['present', 'absent'], default='present'), + )) + + module = AnsibleModule( + argument_spec=argument_spec, + required_together=exo_dns_required_together(), + required_if=[ + ('state', 'present', ['content']), + ('multiple', True, ['content']), + ], + supports_check_mode=True, + ) + + exo_dns_record = ExoDnsRecord(module) + if module.params.get('state') == "present": + resource = exo_dns_record.present_record() + else: + resource = exo_dns_record.absent_record() + + result = exo_dns_record.get_result(resource) + module.exit_json(**result) + + +if __name__ == '__main__': + main() |