From 6d7004f36762102566313ee8a51091a6b783242f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20L=C3=A9one?= Date: Mon, 24 Sep 2018 16:14:23 +0200 Subject: [PATCH] Add a Scaleway IP module (#45121) - Add an option to enable public ip at server creation --- lib/ansible/module_utils/scaleway.py | 3 + .../cloud/scaleway/scaleway_compute.py | 1 + .../modules/cloud/scaleway/scaleway_ip.py | 257 ++++++++++ .../roles/scaleway_ip/defaults/main.yml | 7 + test/legacy/roles/scaleway_ip/tasks/main.yml | 441 ++++++++++++++++++ test/legacy/scaleway.yml | 1 + 6 files changed, 710 insertions(+) create mode 100644 lib/ansible/modules/cloud/scaleway/scaleway_ip.py create mode 100644 test/legacy/roles/scaleway_ip/defaults/main.yml create mode 100644 test/legacy/roles/scaleway_ip/tasks/main.yml diff --git a/lib/ansible/module_utils/scaleway.py b/lib/ansible/module_utils/scaleway.py index 34577c398e..51e18ce04b 100644 --- a/lib/ansible/module_utils/scaleway.py +++ b/lib/ansible/module_utils/scaleway.py @@ -116,6 +116,9 @@ class Scaleway(object): def update(self, path, data=None, headers=None): return self.send("UPDATE", path, data, headers) + def warn(self, x): + self.module.warn(str(x)) + SCALEWAY_LOCATION = { 'par1': {'name': 'Paris 1', 'country': 'FR', "api_endpoint": 'https://cp-par1.scaleway.com'}, diff --git a/lib/ansible/modules/cloud/scaleway/scaleway_compute.py b/lib/ansible/modules/cloud/scaleway/scaleway_compute.py index ba97f2795a..120c3b7670 100644 --- a/lib/ansible/modules/cloud/scaleway/scaleway_compute.py +++ b/lib/ansible/modules/cloud/scaleway/scaleway_compute.py @@ -608,6 +608,7 @@ def core(module): "name": module.params["name"], "commercial_type": module.params["commercial_type"], "enable_ipv6": module.params["enable_ipv6"], + "dynamic_ip_required": module.params["dynamic_ip_required"], "tags": module.params["tags"], "organization": module.params["organization"] } diff --git a/lib/ansible/modules/cloud/scaleway/scaleway_ip.py b/lib/ansible/modules/cloud/scaleway/scaleway_ip.py new file mode 100644 index 0000000000..11b142b05c --- /dev/null +++ b/lib/ansible/modules/cloud/scaleway/scaleway_ip.py @@ -0,0 +1,257 @@ +#!/usr/bin/python +# +# Scaleway IP management module +# +# 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 + +ANSIBLE_METADATA = { + 'metadata_version': '1.1', + 'status': ['preview'], + 'supported_by': 'community' +} + +DOCUMENTATION = ''' +--- +module: scaleway_ip +short_description: Scaleway IP management module +version_added: "2.8" +author: Remy Leone (@sieben) +description: + - This module manages IP on Scaleway account + U(https://developer.scaleway.com) +extends_documentation_fragment: scaleway + +options: + state: + description: + - Indicate desired state of the IP. + default: present + choices: + - present + - absent + + organization: + description: + - Scaleway organization identifier + required: true + + region: + description: + - Scaleway region to use (for example par1). + required: true + choices: + - ams1 + - EMEA-NL-EVS + - par1 + - EMEA-FR-PAR1 + + id: + description: + - id of the Scaleway IP (UUID) + + server: + description: + - id of the server you want to attach an IP to. + - To unattach an IP don't specify this option + + reverse: + description: + - Reverse to assign to the IP +''' + +EXAMPLES = ''' + - name: Create an IP + scaleway_ip: + organization: '{{ scw_org }}' + state: present + region: par1 + register: ip_creation_task + + - name: Make sure IP deleted + scaleway_ip: + id: '{{ ip_creation_task.scaleway_ip.id }}' + state: absent + region: par1 + +''' + +RETURN = ''' +data: + description: This is only present when C(state=present) + returned: when C(state=present) + type: dict + sample: { + "ips": [ + { + "organization": "951df375-e094-4d26-97c1-ba548eeb9c42", + "reverse": null, + "id": "dd9e8df6-6775-4863-b517-e0b0ee3d7477", + "server": { + "id": "3f1568ca-b1a2-4e98-b6f7-31a0588157f1", + "name": "ansible_tuto-1" + }, + "address": "212.47.232.136" + } + ] + } +''' + +from ansible.module_utils.scaleway import SCALEWAY_LOCATION, scaleway_argument_spec, Scaleway +from ansible.module_utils.basic import AnsibleModule + + +def ip_attributes_should_be_changed(api, target_ip, wished_ip): + patch_payload = {} + + if target_ip["reverse"] != wished_ip["reverse"]: + patch_payload["reverse"] = wished_ip["reverse"] + + # IP is assigned to a server + if target_ip["server"] is None and wished_ip["server"]: + patch_payload["server"] = wished_ip["server"] + + # IP is unassigned to a server + try: + if target_ip["server"]["id"] and wished_ip["server"] is None: + patch_payload["server"] = wished_ip["server"] + except (TypeError, KeyError): + pass + + # IP is migrated between 2 different servers + try: + if target_ip["server"]["id"] != wished_ip["server"]: + patch_payload["server"] = wished_ip["server"] + except (TypeError, KeyError): + pass + + return patch_payload + + +def payload_from_wished_ip(wished_ip): + return dict( + (k, v) + for k, v in wished_ip.items() + if k != 'id' and v is not None + ) + + +def present_strategy(api, wished_ip): + changed = False + + response = api.get('ips') + if not response.ok: + api.module.fail_json(msg='Error getting IPs [{0}: {1}]'.format( + response.status_code, response.json['message'])) + + ips_list = response.json["ips"] + ip_lookup = dict((ip["id"], ip) + for ip in ips_list) + + if wished_ip["id"] not in ip_lookup.keys(): + changed = True + if api.module.check_mode: + return changed, {"status": "An IP would be created."} + + # Create IP + creation_response = api.post('/ips', + data=payload_from_wished_ip(wished_ip)) + + if not creation_response.ok: + msg = "Error during ip creation: %s: '%s' (%s)" % (creation_response.info['msg'], + creation_response.json['message'], + creation_response.json) + api.module.fail_json(msg=msg) + return changed, creation_response.json["ip"] + + target_ip = ip_lookup[wished_ip["id"]] + patch_payload = ip_attributes_should_be_changed(api=api, target_ip=target_ip, wished_ip=wished_ip) + + if not patch_payload: + return changed, target_ip + + changed = True + if api.module.check_mode: + return changed, {"status": "IP attributes would be changed."} + + ip_patch_response = api.patch(path="ips/%s" % target_ip["id"], + data=patch_payload) + + if not ip_patch_response.ok: + api.module.fail_json(msg='Error during IP attributes update: [{0}: {1}]'.format( + ip_patch_response.status_code, ip_patch_response.json['message'])) + + return changed, ip_patch_response.json["ip"] + + +def absent_strategy(api, wished_ip): + response = api.get('ips') + changed = False + + status_code = response.status_code + ips_json = response.json + ips_list = ips_json["ips"] + + if not response.ok: + api.module.fail_json(msg='Error getting IPs [{0}: {1}]'.format( + status_code, response.json['message'])) + + ip_lookup = dict((ip["id"], ip) + for ip in ips_list) + if wished_ip["id"] not in ip_lookup.keys(): + return changed, {} + + changed = True + if api.module.check_mode: + return changed, {"status": "IP would be destroyed"} + + response = api.delete('/ips/' + wished_ip["id"]) + if not response.ok: + api.module.fail_json(msg='Error deleting IP [{0}: {1}]'.format( + response.status_code, response.json)) + + return changed, response.json + + +def core(module): + wished_ip = { + "organization": module.params['organization'], + "reverse": module.params["reverse"], + "id": module.params["id"], + "server": module.params["server"] + } + + region = module.params["region"] + module.params['api_url'] = SCALEWAY_LOCATION[region]["api_endpoint"] + + api = Scaleway(module=module) + if module.params["state"] == "absent": + changed, summary = absent_strategy(api=api, wished_ip=wished_ip) + else: + changed, summary = present_strategy(api=api, wished_ip=wished_ip) + module.exit_json(changed=changed, scaleway_ip=summary) + + +def main(): + argument_spec = scaleway_argument_spec() + argument_spec.update(dict( + state=dict(default='present', choices=['absent', 'present']), + organization=dict(required=True), + server=dict(), + reverse=dict(), + region=dict(required=True, choices=SCALEWAY_LOCATION.keys()), + id=dict() + )) + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + ) + + core(module) + + +if __name__ == '__main__': + main() diff --git a/test/legacy/roles/scaleway_ip/defaults/main.yml b/test/legacy/roles/scaleway_ip/defaults/main.yml new file mode 100644 index 0000000000..fe7aa93baf --- /dev/null +++ b/test/legacy/roles/scaleway_ip/defaults/main.yml @@ -0,0 +1,7 @@ +--- +scaleway_organization: '{{ scw_org }}' +scaleway_region: ams1 +scaleway_image_id: 89ee4018-f8c3-4dc4-a6b5-bca14f985ebe +scaleway_commerial_type: START1-S +scaleway_server_name: scaleway_ip_test_server +scaleway_reverse_name: scaleway.com diff --git a/test/legacy/roles/scaleway_ip/tasks/main.yml b/test/legacy/roles/scaleway_ip/tasks/main.yml new file mode 100644 index 0000000000..9b639ad027 --- /dev/null +++ b/test/legacy/roles/scaleway_ip/tasks/main.yml @@ -0,0 +1,441 @@ +# SCW_API_KEY='XXX' SCW_ORG='YYY' ansible-playbook ./test/legacy/scaleway.yml --tags test_scaleway_ip + +- name: Create IP (Check) + check_mode: yes + scaleway_ip: + state: present + region: '{{ scaleway_region }}' + organization: '{{ scaleway_organization }}' + register: ip_creation_check_task + +- debug: var=ip_creation_check_task + +- name: ip_creation_check_task is success + assert: + that: + - ip_creation_check_task is success + +- name: ip_creation_check_task is changed + assert: + that: + - ip_creation_check_task is changed + +- name: Create IP + scaleway_ip: + state: present + region: '{{ scaleway_region }}' + organization: '{{ scaleway_organization }}' + register: ip_creation_task + +- debug: var=ip_creation_task + +- name: ip_creation_task is success + assert: + that: + - ip_creation_task is success + +- name: ip_creation_task is changed + assert: + that: + - ip_creation_task is changed + +- name: ip_creation_task.scaleway_ip.server is none + assert: + that: + - '{{ ip_creation_task.scaleway_ip.server is none }}' + +- name: Create IP (Confirmation) + scaleway_ip: + id: '{{ ip_creation_task.scaleway_ip.id }}' + state: present + region: '{{ scaleway_region }}' + organization: '{{ scaleway_organization }}' + register: ip_creation_confirmation_task + +- debug: var=ip_creation_confirmation_task + +- name: ip_creation_confirmation_task is success + assert: + that: + - ip_creation_confirmation_task is success + +- name: ip_creation_confirmation_task is not changed + assert: + that: + - ip_creation_confirmation_task is not changed + +- name: ip_creation_confirmation_task.scaleway_ip.server is none + assert: + that: + - '{{ ip_creation_task.scaleway_ip.server is none }}' + +- name: Assign reverse to server (Check) + check_mode: yes + scaleway_ip: + state: present + id: '{{ ip_creation_task.scaleway_ip.id }}' + region: '{{ scaleway_region }}' + organization: '{{ scaleway_organization }}' + reverse: '{{ scaleway_reverse_name }}' + register: ip_reverse_assignation_check_task + +- debug: var=ip_reverse_assignation_check_task + +- name: ip_reverse_assignation_check_task is success + assert: + that: + - ip_reverse_assignation_check_task is success + +- name: ip_reverse_assignation_check_task is success + assert: + that: + - ip_reverse_assignation_check_task is success + +- name: Assign reverse to an IP + scaleway_ip: + state: present + id: '{{ ip_creation_task.scaleway_ip.id }}' + region: '{{ scaleway_region }}' + organization: '{{ scaleway_organization }}' + reverse: '{{ scaleway_reverse_name }}' + register: ip_reverse_assignation_task + +- debug: var=ip_reverse_assignation_task + +- name: ip_reverse_assignation_task is success + assert: + that: + - ip_reverse_assignation_task is success + +- name: ip_reverse_assignation_task is changed + assert: + that: + - ip_reverse_assignation_task is changed + +- name: Assign reverse to an IP (Confirmation) + scaleway_ip: + state: present + id: '{{ ip_creation_task.scaleway_ip.id }}' + region: '{{ scaleway_region }}' + organization: '{{ scaleway_organization }}' + reverse: '{{ scaleway_reverse_name }}' + register: ip_reverse_assignation_confirmation_task + +- debug: var=ip_reverse_assignation_confirmation_task + +- name: ip_reverse_assignation_confirmation_task is success + assert: + that: + - ip_reverse_assignation_confirmation_task is success + +- name: ip_reverse_assignation_confirmation_task is not changed + assert: + that: + - ip_reverse_assignation_confirmation_task is not changed + +- name: Create a server + scaleway_compute: + state: present + name: '{{ scaleway_server_name }}' + image: '{{ scaleway_image_id }}' + organization: '{{ scaleway_organization }}' + region: '{{ scaleway_region }}' + commercial_type: '{{ scaleway_commerial_type }}' + dynamic_ip_required: false + wait: true + + register: server_creation_task + +- debug: var=server_creation_task + +- name: server_creation_task is success + assert: + that: + - server_creation_task is success + +- name: Assign IP to server (Check) + check_mode: yes + scaleway_ip: + state: present + id: '{{ ip_creation_task.scaleway_ip.id }}' + region: '{{ scaleway_region }}' + organization: '{{ scaleway_organization }}' + server: '{{ server_creation_task.msg.id }}' + reverse: '{{ scaleway_reverse_name }}' + register: ip_assignation_check_task + +- debug: var=ip_assignation_check_task + +- name: ip_assignation_check_task is success + assert: + that: + - ip_assignation_check_task is success + +- name: ip_assignation_check_task is success + assert: + that: + - ip_assignation_check_task is success + +- name: Assign IP to server + scaleway_ip: + state: present + id: '{{ ip_creation_task.scaleway_ip.id }}' + region: '{{ scaleway_region }}' + organization: '{{ scaleway_organization }}' + server: '{{ server_creation_task.msg.id }}' + reverse: '{{ scaleway_reverse_name }}' + register: ip_assignation_task + +- debug: var=ip_assignation_task + +- name: ip_assignation_task is success + assert: + that: + - ip_assignation_task is success + +- name: ip_assignation_task is changed + assert: + that: + - ip_assignation_task is changed + +- name: Assign IP to server (Confirmation) + scaleway_ip: + state: present + id: '{{ ip_creation_task.scaleway_ip.id }}' + region: '{{ scaleway_region }}' + organization: '{{ scaleway_organization }}' + server: '{{ server_creation_task.msg.id }}' + reverse: '{{ scaleway_reverse_name }}' + register: ip_assignation_confirmation_task + +- debug: var=ip_assignation_confirmation_task + +- name: ip_assignation_confirmation_task is success + assert: + that: + - ip_assignation_confirmation_task is success + +- name: ip_assignation_confirmation_task is not changed + assert: + that: + - ip_assignation_confirmation_task is not changed + +- name: Unassign IP to server (Check) + check_mode: yes + scaleway_ip: + state: present + id: '{{ ip_creation_task.scaleway_ip.id }}' + region: '{{ scaleway_region }}' + organization: '{{ scaleway_organization }}' + reverse: '{{ scaleway_reverse_name }}' + register: ip_unassignation_check_task + +- debug: var=ip_unassignation_check_task + +- name: ip_unassignation_check_task is success + assert: + that: + - ip_unassignation_check_task is success + +- name: ip_unassignation_check_task is changed + assert: + that: + - ip_unassignation_check_task is changed + +- name: Unassign IP to server + scaleway_ip: + state: present + id: '{{ ip_creation_task.scaleway_ip.id }}' + region: '{{ scaleway_region }}' + organization: '{{ scaleway_organization }}' + reverse: '{{ scaleway_reverse_name }}' + register: ip_unassignation_task + +- debug: var=ip_unassignation_task + +- name: ip_unassignation_task is success + assert: + that: + - ip_unassignation_task is success + +- name: ip_unassignation_task is changed + assert: + that: + - ip_unassignation_task is changed + +- name: ip_unassignation_task.scaleway_ip.server is none + assert: + that: + - '{{ ip_unassignation_task.scaleway_ip.server is none }}' + +- name: Unassign IP to server (Confirmation) + scaleway_ip: + state: present + id: '{{ ip_creation_task.scaleway_ip.id }}' + region: '{{ scaleway_region }}' + organization: '{{ scaleway_organization }}' + reverse: '{{ scaleway_reverse_name }}' + register: ip_unassignation_confirmation_task + +- debug: var=ip_unassignation_confirmation_task + +- name: ip_unassignation_confirmation_task is success + assert: + that: + - ip_unassignation_confirmation_task is success + +- name: ip_unassignation_confirmation_task is not changed + assert: + that: + - ip_unassignation_confirmation_task is not changed + +- name: ip_unassignation_confirmation_task.scaleway_ip.server is none + assert: + that: + - '{{ ip_unassignation_task.scaleway_ip.server is none }}' + +- name: Unassign reverse to IP (Check) + check_mode: yes + scaleway_ip: + state: present + id: '{{ ip_creation_task.scaleway_ip.id }}' + region: '{{ scaleway_region }}' + organization: '{{ scaleway_organization }}' + register: ip_reverse_unassignation_check_task + +- debug: var=ip_reverse_unassignation_check_task + +- name: ip_reverse_unassignation_check_task is success + assert: + that: + - ip_reverse_unassignation_check_task is success + +- name: ip_reverse_unassignation_check_task is changed + assert: + that: + - ip_reverse_unassignation_check_task is changed + +- name: Unassign reverse to an IP + scaleway_ip: + state: present + id: '{{ ip_creation_task.scaleway_ip.id }}' + region: '{{ scaleway_region }}' + organization: '{{ scaleway_organization }}' + register: ip_reverse_unassignation_task + +- debug: var=ip_reverse_unassignation_task + +- name: ip_reverse_unassignation_task is success + assert: + that: + - ip_reverse_unassignation_task is success + +- name: ip_reverse_unassignation_task is changed + assert: + that: + - ip_reverse_unassignation_task is changed + +- name: ip_reverse_unassignation_task.scaleway_ip.reverse is none + assert: + that: + - '{{ ip_reverse_unassignation_task.scaleway_ip.reverse is none }}' + +- name: Unassign reverse to an IP (Confirmation) + scaleway_ip: + state: present + id: '{{ ip_creation_task.scaleway_ip.id }}' + region: '{{ scaleway_region }}' + organization: '{{ scaleway_organization }}' + register: ip_reverse_unassignation_confirmation_task + +- debug: var=ip_reverse_unassignation_confirmation_task + +- name: ip_reverse_unassignation_confirmation_task is success + assert: + that: + - ip_reverse_unassignation_confirmation_task is success + +- name: ip_reverse_unassignation_confirmation_task is not changed + assert: + that: + - ip_reverse_unassignation_confirmation_task is not changed + +- name: ip_reverse_unassignation_confirmation_task.scaleway_ip.server is none + assert: + that: + - '{{ ip_reverse_unassignation_confirmation_task.scaleway_ip.reverse is none }}' + +- name: Destroy a server + scaleway_compute: + name: '{{ scaleway_server_name }}' + state: absent + image: '{{ scaleway_image_id }}' + organization: '{{ scaleway_organization }}' + region: '{{ scaleway_region }}' + commercial_type: '{{ scaleway_commerial_type }}' + wait: true + register: server_destroy_task + +- debug: var=server_destroy_task + +- name: server_destroy_task is success + assert: + that: + - server_destroy_task is success + +- name: server_destroy_task is changed + assert: + that: + - server_destroy_task is changed + +- name: Delete IP (Check) + check_mode: yes + scaleway_ip: + state: absent + region: '{{ scaleway_region }}' + id: '{{ ip_creation_task.scaleway_ip.id }}' + register: ip_deletion_check_task + +- name: ip_deletion_check_task is success + assert: + that: + - ip_deletion_check_task is success + +- name: ip_deletion_check_task is changed + assert: + that: + - ip_deletion_check_task is changed + +- name: Delete IP + scaleway_ip: + state: absent + region: '{{ scaleway_region }}' + id: '{{ ip_creation_task.scaleway_ip.id }}' + register: ip_deletion_task + +- name: ip_deletion_task is success + assert: + that: + - ip_deletion_task is success + +- name: ip_deletion_task is changed + assert: + that: + - ip_deletion_task is changed + +- name: Delete IP (Confirmation) + scaleway_ip: + state: absent + region: '{{ scaleway_region }}' + id: '{{ ip_creation_task.scaleway_ip.id }}' + register: ip_deletion_confirmation_task + +- name: ip_deletion_confirmation_task is success + assert: + that: + - ip_deletion_confirmation_task is success + +- name: ip_deletion_confirmation_task is not changed + assert: + that: + - ip_deletion_confirmation_task is not changed diff --git a/test/legacy/scaleway.yml b/test/legacy/scaleway.yml index 4b3b2b1bda..b242d99c29 100644 --- a/test/legacy/scaleway.yml +++ b/test/legacy/scaleway.yml @@ -8,6 +8,7 @@ roles: - { role: scaleway_compute, tags: test_scaleway_compute } - { role: scaleway_image_facts, tags: test_scaleway_image_facts } + - { role: scaleway_ip, tags: test_scaleway_ip } - { role: scaleway_ip_facts, tags: test_scaleway_ip_facts } - { role: scaleway_organization_facts, tags: test_scaleway_organization_facts } - { role: scaleway_security_group_facts, tags: test_scaleway_security_group_facts }