From ffd107db4ddeff265665769247debd9d71c4cb8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Moser?= Date: Tue, 16 Jun 2020 19:19:06 +0200 Subject: [PATCH] dedicated collection for exoscale.com related plugins (#519) --- .github/BOTMETA.yml | 2 - plugins/doc_fragments/exoscale.py | 56 --- plugins/module_utils/exoscale.py | 139 ------- plugins/modules/exo_dns_domain.py | 1 - plugins/modules/exo_dns_record.py | 1 - .../net_tools/exoscale/exo_dns_domain.py | 205 ----------- .../net_tools/exoscale/exo_dns_record.py | 340 ------------------ 7 files changed, 744 deletions(-) delete mode 100644 plugins/doc_fragments/exoscale.py delete mode 100644 plugins/module_utils/exoscale.py delete mode 120000 plugins/modules/exo_dns_domain.py delete mode 120000 plugins/modules/exo_dns_record.py delete mode 100644 plugins/modules/net_tools/exoscale/exo_dns_domain.py delete mode 100644 plugins/modules/net_tools/exoscale/exo_dns_record.py diff --git a/.github/BOTMETA.yml b/.github/BOTMETA.yml index 2ef360a59b..ffe7d39d94 100644 --- a/.github/BOTMETA.yml +++ b/.github/BOTMETA.yml @@ -713,8 +713,6 @@ files: authors: drcapulet $modules/net_tools/dnsmadeeasy.py: authors: briceburg - $modules/net_tools/exoscale/: - authors: resmo $modules/net_tools/haproxy.py: authors: ravibhure $modules/net_tools/: diff --git a/plugins/doc_fragments/exoscale.py b/plugins/doc_fragments/exoscale.py deleted file mode 100644 index 32719807b7..0000000000 --- a/plugins/doc_fragments/exoscale.py +++ /dev/null @@ -1,56 +0,0 @@ -# -*- coding: utf-8 -*- - -# Copyright: (c) 2017, René Moser -# 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. - - Since 2.4, the ENV variable C(CLOUDSTACK_KEY) is used as default, when defined. - type: str - api_secret: - description: - - Secret key of the Exoscale DNS API. - - Since 2.4, the ENV variable C(CLOUDSTACK_SECRET) is used as default, when defined. - type: str - api_timeout: - description: - - HTTP timeout to Exoscale DNS API. - - Since 2.4, 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. - - Since 2.4, 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/plugins/module_utils/exoscale.py b/plugins/module_utils/exoscale.py deleted file mode 100644 index e56f27144f..0000000000 --- a/plugins/module_utils/exoscale.py +++ /dev/null @@ -1,139 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2016, René Moser -# Simplified BSD License (see licenses/simplified_bsd.txt or https://opensource.org/licenses/BSD-2-Clause) - -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(default=os.environ.get('CLOUDSTACK_KEY'), no_log=True), - api_secret=dict(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(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/plugins/modules/exo_dns_domain.py b/plugins/modules/exo_dns_domain.py deleted file mode 120000 index 5f95c1da03..0000000000 --- a/plugins/modules/exo_dns_domain.py +++ /dev/null @@ -1 +0,0 @@ -./net_tools/exoscale/exo_dns_domain.py \ No newline at end of file diff --git a/plugins/modules/exo_dns_record.py b/plugins/modules/exo_dns_record.py deleted file mode 120000 index 9abc8b3bb5..0000000000 --- a/plugins/modules/exo_dns_record.py +++ /dev/null @@ -1 +0,0 @@ -./net_tools/exoscale/exo_dns_record.py \ No newline at end of file diff --git a/plugins/modules/net_tools/exoscale/exo_dns_domain.py b/plugins/modules/net_tools/exoscale/exo_dns_domain.py deleted file mode 100644 index b64e30b642..0000000000 --- a/plugins/modules/net_tools/exoscale/exo_dns_domain.py +++ /dev/null @@ -1,205 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- -# -# (c) 2016, René Moser -# 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)" -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: -- community.general.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 ansible_collections.community.general.plugins.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/plugins/modules/net_tools/exoscale/exo_dns_record.py b/plugins/modules/net_tools/exoscale/exo_dns_record.py deleted file mode 100644 index 16898e01c1..0000000000 --- a/plugins/modules/net_tools/exoscale/exo_dns_record.py +++ /dev/null @@ -1,340 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- -# -# (c) 2016, René Moser -# 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)" -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: -- community.general.exoscale - -''' - -EXAMPLES = ''' -- name: Create or update an A record - 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 - exo_dns_record: - name: web-vm-1 - domain: example.com - content: 1.2.3.5 - -- name: Create another A record with same name - exo_dns_record: - name: web-vm-1 - domain: example.com - content: 1.2.3.6 - multiple: yes - -- name: Create or update a CNAME record - exo_dns_record: - name: www - domain: example.com - record_type: CNAME - content: web-vm-1 - -- name: Create another MX record - 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 - exo_dns_record: - domain: example.com - record_type: MX - content: mx1.example.com - multiple: yes - state: absent - -- name: Remove a single A record - 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 ansible_collections.community.general.plugins.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()