#!/usr/bin/python # # Scaleway IP management module # # Copyright (c) Ansible project # GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) # SPDX-License-Identifier: GPL-3.0-or-later from __future__ import annotations DOCUMENTATION = r""" module: scaleway_ip short_description: Scaleway IP management module author: Remy Leone (@remyleone) description: - This module manages IP on Scaleway account U(https://developer.scaleway.com). extends_documentation_fragment: - community.general.scaleway - community.general.attributes - community.general.scaleway.actiongroup_scaleway attributes: check_mode: support: full diff_mode: support: none action_group: version_added: 11.3.0 options: state: type: str description: - Indicate desired state of the IP. default: present choices: - present - absent organization: type: str description: - Scaleway organization identifier. - Exactly one of O(project) and O(organization) must be specified. project: type: str description: - Project identifier. - Exactly one of O(project) and O(organization) must be specified. version_added: 12.3.0 region: type: str description: - Scaleway region to use (for example par1). required: true choices: - ams1 - EMEA-NL-EVS - ams2 - ams3 - par1 - EMEA-FR-PAR1 - par2 - EMEA-FR-PAR2 - par3 - waw1 - EMEA-PL-WAW1 - waw2 - waw3 id: type: str description: - ID of the Scaleway IP (UUID). server: type: str description: - ID of the server you want to attach an IP to. - To unattach an IP do not specify this option. reverse: type: str description: - Reverse to assign to the IP. """ EXAMPLES = r""" - name: Create an IP with a project ID community.general.scaleway_ip: project: '{{ project_id }}' state: present region: par1 register: ip_creation_task - name: Make sure IP deleted community.general.scaleway_ip: id: '{{ ip_creation_task.scaleway_ip.id }}' state: absent region: par1 - name: Create an IP in the default project with an organization ID (deprecated) community.general.scaleway_ip: organization: '{{ scw_org }}' state: present region: par1 register: ip_creation_task """ RETURN = r""" data: description: This is only present when O(state=present). returned: when O(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.basic import AnsibleModule from ansible_collections.community.general.plugins.module_utils.scaleway import ( SCALEWAY_LOCATION, Scaleway, scaleway_argument_spec, ) 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 {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=f"Error getting IPs [{response.status_code}: {response.json['message']}]") ips_list = response.json["ips"] ip_lookup = {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 = f"Error during ip creation: {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=f"ips/{target_ip['id']}", data=patch_payload) if not ip_patch_response.ok: api.module.fail_json( msg=f"Error during IP attributes update: [{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=f"Error getting IPs [{status_code}: {response.json['message']}]") ip_lookup = {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(f"/ips/{wished_ip['id']}") if not response.ok: api.module.fail_json(msg=f"Error deleting IP [{response.status_code}: {response.json}]") return changed, response.json def core(module): wished_ip = { "organization": module.params["organization"], "project": module.params["project"], "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(), project=dict(), server=dict(), reverse=dict(), region=dict(required=True, choices=list(SCALEWAY_LOCATION.keys())), id=dict(), ) ) module = AnsibleModule( argument_spec=argument_spec, supports_check_mode=True, mutually_exclusive=[ ("organization", "project"), ], required_one_of=[ ("organization", "project"), ], ) core(module) if __name__ == "__main__": main()