diff --git a/lib/ansible/module_utils/exoscale.py b/lib/ansible/module_utils/exoscale.py new file mode 100644 index 00000000000..64118a5e1dc --- /dev/null +++ b/lib/ansible/module_utils/exoscale.py @@ -0,0 +1,167 @@ +# -*- coding: utf-8 -*- +# +# (c) 2016, René Moser +# +# This code is part of Ansible, but is an independent component. +# This particular file snippet, and this file snippet only, is BSD licensed. +# Modules you write using this snippet, which is embedded dynamically by Ansible +# still belong to the author of the module, and may assign their own license +# to the complete work. +# +# Redistribution and use in source and binary forms, with or without modification, +# are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +# IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +try: + import json +except ImportError: + try: + import simplejson as json + except ImportError: + # Let snippet from module_utils/basic.py return a proper error in this case + pass + +import urllib +from ConfigParser import ConfigParser, NoSectionError + +# import module snippets +from ansible.module_utils.urls import * +from ansible.module_utils.basic import * + +EXO_DNS_BASEURL="https://api.exoscale.ch/dns/v1" + +def exo_dns_argument_spec(): + return dict( + api_key=dict(default=None, no_log=True), + api_secret=dict(default=None, no_log=True), + api_timeout=dict(type='int', default=10), + api_region=dict(default='cloudstack'), + validate_certs=dict(default='yes', 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: + e = get_exception() + self.module.fail_json(msg=str(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_{0}'.format(key.upper()) not in os.environ: + break + else: + env_conf[key] = os.environ['CLOUDSTACK_{0}'.format(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]): + raise SystemExit("Config file not found. Tried {0}".format( + ", ".join(paths))) + + conf = 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 = json.dumps(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 json.load(response) + except Exception: + return {} + + def has_changed(self, want_dict, current_dict, only_keys=None): + changed = False + for key, value in want_dict.iteritems(): + # 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], (int, long, float, complex)): + if value != current_dict[key]: + self.result['diff']['before'][key] = current_dict[key] + self.result['diff']['after'][key] = value + changed = True + elif value.lower() != current_dict[key].encode('utf-8').lower(): + self.result['diff']['before'][key] = current_dict[key] + self.result['diff']['after'][key] = value + changed = True + else: + self.result['diff']['after'][key] = value + changed = True + return changed + diff --git a/test/integration/Makefile b/test/integration/Makefile index e4c3dcc4461..99fc56a164d 100644 --- a/test/integration/Makefile +++ b/test/integration/Makefile @@ -267,6 +267,11 @@ cloudstack: RC=$$? ; \ exit $$RC; +exoscale: + ansible-playbook exoscale.yml -i $(INVENTORY) -e @$(VARS_FILE) -v $(TEST_FLAGS) ; \ + RC=$$? ; \ + exit $$RC; + cloudflare: $(CREDENTIALS_FILE) ansible-playbook cloudflare.yml -i $(INVENTORY) -e @$(VARS_FILE) -e @$(CREDENTIALS_FILE) -e "resource_prefix=$(CLOUD_RESOURCE_PREFIX)" -v $(TEST_FLAGS) ; \ RC=$$? ; \ diff --git a/test/integration/exoscale.yml b/test/integration/exoscale.yml new file mode 100644 index 00000000000..a6f5621bd28 --- /dev/null +++ b/test/integration/exoscale.yml @@ -0,0 +1,5 @@ +--- +- hosts: localhost + gather_facts: no + roles: + - { role: test_exoscale_dns, tags: test_exoscale_dns } diff --git a/test/integration/roles/test_exoscale_dns/defaults/main.yml b/test/integration/roles/test_exoscale_dns/defaults/main.yml new file mode 100644 index 00000000000..a98c86904dc --- /dev/null +++ b/test/integration/roles/test_exoscale_dns/defaults/main.yml @@ -0,0 +1,4 @@ +--- +exo_dns_domain_name: example.com +exo_dns_record_name_web: web +exo_dns_record_name_mx: mx diff --git a/test/integration/roles/test_exoscale_dns/tasks/main.yml b/test/integration/roles/test_exoscale_dns/tasks/main.yml new file mode 100644 index 00000000000..96e5da0f29b --- /dev/null +++ b/test/integration/roles/test_exoscale_dns/tasks/main.yml @@ -0,0 +1,399 @@ +--- +- name: setup + local_action: + module: exo_dns_domain + name: "{{ exo_dns_domain_name }}" + state: absent + register: result +- name: verify setup + assert: + that: + - result|success + +- name: test fail if missing name + local_action: + module: exo_dns_domain + register: result + ignore_errors: true +- name: verify results of fail if missing params + assert: + that: + - result|failed + - 'result.msg == "missing required arguments: name"' + +- name: test create a domain + local_action: + module: exo_dns_domain + name: "{{ exo_dns_domain_name }}" + register: result +- name: verify results of test create a domain + assert: + that: + - result|changed + - 'result.exo_dns_domain.name == "{{ exo_dns_domain_name }}"' + +- name: test create a domain idempotence + local_action: + module: exo_dns_domain + name: "{{ exo_dns_domain_name }}" + register: result +- name: verify results of test create a domain idempotence + assert: + that: + - not result|changed + - 'result.exo_dns_domain.name == "{{ exo_dns_domain_name }}"' + +- name: test fail if missing required params + local_action: + module: exo_dns_record + register: result + ignore_errors: true +- name: verify results of test fail if missing required params + assert: + that: + - result|failed + - 'result.msg == "missing required arguments: domain"' + +- name: test fail if missing required params + local_action: + module: exo_dns_record + domain: "{{ exo_dns_domain_name }}" + state: absent + register: result + ignore_errors: true +- name: verify results of test fail if missing required params + assert: + that: + - result|failed + - 'result.msg == "name is but the following are missing: content"' + +- name: test fail if missing required params state=present + local_action: + module: exo_dns_record + domain: "{{ exo_dns_domain_name }}" + name: "" + register: result + ignore_errors: true +- name: verify results of test fail if missing required params state=present + assert: + that: + - result|failed + - 'result.msg == "state is present but the following are missing: content"' + +- name: test fail if missing required params state=absent + local_action: + module: exo_dns_record + domain: "{{ exo_dns_domain_name }}" + name: "" + state: absent + register: result + ignore_errors: true +- name: verify results of test fail if missing required params state=absent + assert: + that: + - result|failed + - 'result.msg == "name is but the following are missing: content"' + +- name: test create a record + local_action: + module: exo_dns_record + domain: "{{ exo_dns_domain_name }}" + name: "{{ exo_dns_record_name_web }}" + content: 1.2.3.4 + register: result +- name: verify results of test create a record + assert: + that: + - result|changed + - 'result.exo_dns_record.name == "{{ exo_dns_record_name_web }}"' + - 'result.exo_dns_record.domain == "{{ exo_dns_domain_name }}"' + - 'result.exo_dns_record.content == "1.2.3.4"' + +- name: test create a record idempotence + local_action: + module: exo_dns_record + domain: "{{ exo_dns_domain_name }}" + name: "{{ exo_dns_record_name_web }}" + content: 1.2.3.4 + register: result +- name: verify results of test create a record + assert: + that: + - not result|changed + - 'result.exo_dns_record.name == "{{ exo_dns_record_name_web }}"' + - 'result.exo_dns_record.domain == "{{ exo_dns_domain_name }}"' + - 'result.exo_dns_record.content == "1.2.3.4"' + +- name: test update a record + local_action: + module: exo_dns_record + domain: "{{ exo_dns_domain_name }}" + name: "{{ exo_dns_record_name_web }}" + content: 1.2.3.5 + ttl: 7200 + register: result +- name: verify results of test update a record + assert: + that: + - result|changed + - 'result.exo_dns_record.name == "{{ exo_dns_record_name_web }}"' + - 'result.exo_dns_record.domain == "{{ exo_dns_domain_name }}"' + - 'result.exo_dns_record.content == "1.2.3.5"' + - 'result.exo_dns_record.ttl == 7200' + +- name: test update a record idempotence + local_action: + module: exo_dns_record + domain: "{{ exo_dns_domain_name }}" + name: "{{ exo_dns_record_name_web }}" + content: 1.2.3.5 + ttl: 7200 + register: result +- name: verify results of test update a record idempotence + assert: + that: + - not result|changed + - 'result.exo_dns_record.name == "{{ exo_dns_record_name_web }}"' + - 'result.exo_dns_record.domain == "{{ exo_dns_domain_name }}"' + - 'result.exo_dns_record.content == "1.2.3.5"' + - 'result.exo_dns_record.ttl == 7200' + +- name: test delete a record + local_action: + module: exo_dns_record + domain: "{{ exo_dns_domain_name }}" + name: "{{ exo_dns_record_name_web }}" + state: absent + register: result +- name: verify results of test create a record + assert: + that: + - result|changed + - 'result.exo_dns_record.name == "{{ exo_dns_record_name_web }}"' + - 'result.exo_dns_record.domain == "{{ exo_dns_domain_name }}"' + - 'result.exo_dns_record.content == "1.2.3.5"' + - 'result.exo_dns_record.ttl == 7200' + +- name: test delete a record idempotence + local_action: + module: exo_dns_record + domain: "{{ exo_dns_domain_name }}" + name: "{{ exo_dns_record_name_web }}" + state: absent + register: result +- name: verify results of test create a record idempotence + assert: + that: + - not result|changed + +- name: setup an existing MX record + local_action: + module: exo_dns_record + domain: "{{ exo_dns_domain_name }}" + record_type: MX + name: "" + content: "mx2.{{ exo_dns_domain_name }}" + prio: 10 + register: result +- name: verify results of test create a record + assert: + that: + - result|changed + - 'result.exo_dns_record.name == ""' + - 'result.exo_dns_record.domain == "{{ exo_dns_domain_name }}"' + - 'result.exo_dns_record.content == "mx2.{{ exo_dns_domain_name }}"' + - 'result.exo_dns_record.prio == 10' + +- name: test create a MX record + local_action: + module: exo_dns_record + domain: "{{ exo_dns_domain_name }}" + record_type: MX + name: "" + content: "mx1.{{ exo_dns_domain_name }}" + prio: 10 + register: result +- name: verify results of test create a record + assert: + that: + - result|changed + - 'result.exo_dns_record.name == ""' + - 'result.exo_dns_record.domain == "{{ exo_dns_domain_name }}"' + - 'result.exo_dns_record.content == "mx1.{{ exo_dns_domain_name }}"' + - 'result.exo_dns_record.prio == 10' + +- name: test update a MX record + local_action: + module: exo_dns_record + domain: "{{ exo_dns_domain_name }}" + record_type: MX + name: "" + content: "mx1.{{ exo_dns_domain_name }}" + prio: 20 + tags: foo + register: result +- name: verify results of test create a record + assert: + that: + - result|changed + - 'result.exo_dns_record.name == ""' + - 'result.exo_dns_record.domain == "{{ exo_dns_domain_name }}"' + - 'result.exo_dns_record.content == "mx1.{{ exo_dns_domain_name }}"' + - 'result.exo_dns_record.prio == 20' + tags: foo + +- name: test delete a MX record + local_action: + module: exo_dns_record + domain: "{{ exo_dns_domain_name }}" + record_type: MX + name: "" + content: "mx1.{{ exo_dns_domain_name }}" + state: absent + register: result +- name: verify results of test delete a MX record + assert: + that: + - result|changed + - 'result.exo_dns_record.name == ""' + - 'result.exo_dns_record.domain == "{{ exo_dns_domain_name }}"' + - 'result.exo_dns_record.content == "mx1.{{ exo_dns_domain_name }}"' + - 'result.exo_dns_record.prio == 20' + +- name: test delete a MX record idempotence + local_action: + module: exo_dns_record + domain: "{{ exo_dns_domain_name }}" + record_type: MX + name: "" + content: "mx1.{{ exo_dns_domain_name }}" + state: absent + register: result +- name: verify results of test delete a MX record idempotence + assert: + that: + - not result|changed + +- name: test create first multiple a record + local_action: + module: exo_dns_record + domain: "{{ exo_dns_domain_name }}" + name: "{{ exo_dns_record_name_web }}" + multiple: yes + content: 1.2.3.4 + register: result +- name: verify results of test create first multiple a record + assert: + that: + - result|changed + - 'result.exo_dns_record.name == "{{ exo_dns_record_name_web }}"' + - 'result.exo_dns_record.domain == "{{ exo_dns_domain_name }}"' + - 'result.exo_dns_record.content == "1.2.3.4"' + +- name: test create another similar a record + local_action: + module: exo_dns_record + domain: "{{ exo_dns_domain_name }}" + name: "{{ exo_dns_record_name_web }}" + multiple: yes + content: 1.2.3.5 + register: result +- name: verify results of test create another similar a record + assert: + that: + - result|changed + - 'result.exo_dns_record.name == "{{ exo_dns_record_name_web }}"' + - 'result.exo_dns_record.domain == "{{ exo_dns_domain_name }}"' + - 'result.exo_dns_record.content == "1.2.3.5"' + - 'result.exo_dns_record.ttl == 3600' + +- name: test update another similar a record + local_action: + module: exo_dns_record + domain: "{{ exo_dns_domain_name }}" + name: "{{ exo_dns_record_name_web }}" + multiple: yes + content: 1.2.3.5 + ttl: 7200 + register: result +- name: verify results of test create another similar a record + assert: + that: + - result|changed + - 'result.exo_dns_record.name == "{{ exo_dns_record_name_web }}"' + - 'result.exo_dns_record.domain == "{{ exo_dns_domain_name }}"' + - 'result.exo_dns_record.content == "1.2.3.5"' + - 'result.exo_dns_record.ttl == 7200' + +- name: test create first multiple a record idempotence + local_action: + module: exo_dns_record + domain: "{{ exo_dns_domain_name }}" + name: "{{ exo_dns_record_name_web }}" + multiple: yes + content: 1.2.3.4 + register: result +- name: verify results of test create first multiple a record idempotence + assert: + that: + - not result|changed + - 'result.exo_dns_record.name == "{{ exo_dns_record_name_web }}"' + - 'result.exo_dns_record.domain == "{{ exo_dns_domain_name }}"' + - 'result.exo_dns_record.content == "1.2.3.4"' + +- name: test delete similar a record + local_action: + module: exo_dns_record + domain: "{{ exo_dns_domain_name }}" + name: "{{ exo_dns_record_name_web }}" + multiple: yes + content: 1.2.3.5 + state: absent + register: result +- name: verify results of test delete similar a record + assert: + that: + - result|changed + - 'result.exo_dns_record.name == "{{ exo_dns_record_name_web }}"' + - 'result.exo_dns_record.domain == "{{ exo_dns_domain_name }}"' + - 'result.exo_dns_record.content == "1.2.3.5"' + +- name: test delete first similar a record + local_action: + module: exo_dns_record + domain: "{{ exo_dns_domain_name }}" + name: "{{ exo_dns_record_name_web }}" + multiple: yes + content: 1.2.3.4 + state: absent + register: result +- name: verify results of test delete first similar a record + assert: + that: + - result|changed + - 'result.exo_dns_record.name == "{{ exo_dns_record_name_web }}"' + - 'result.exo_dns_record.domain == "{{ exo_dns_domain_name }}"' + - 'result.exo_dns_record.content == "1.2.3.4"' + +- name: test delete a domain + local_action: + module: exo_dns_domain + name: "{{ exo_dns_domain_name }}" + state: absent + register: result +- name: verify results of test delete a domain + assert: + that: + - result|changed + - 'result.exo_dns_domain.name == "{{ exo_dns_domain_name }}"' + +- name: test delete a domain idempotence + local_action: + module: exo_dns_domain + name: "{{ exo_dns_domain_name }}" + state: absent + register: result +- name: verify results of test delete a domain idempotence + assert: + that: + - not result|changed