From d7f8f9363fe10e1ce588a50d31a6568d5ad7342d Mon Sep 17 00:00:00 2001 From: Jack Ivanov Date: Fri, 16 Aug 2019 12:01:34 +0200 Subject: [PATCH] Scaleway modules upgrade --- library/scaleway_compute.py | 157 +++++++++++++++++++++++----- roles/cloud-scaleway/tasks/main.yml | 1 + 2 files changed, 132 insertions(+), 26 deletions(-) diff --git a/library/scaleway_compute.py b/library/scaleway_compute.py index d36ba09..30301b4 100644 --- a/library/scaleway_compute.py +++ b/library/scaleway_compute.py @@ -29,6 +29,15 @@ extends_documentation_fragment: scaleway options: + public_ip: + description: + - Manage public IP on a Scaleway server + - Could be Scaleway IP address UUID + - C(dynamic) Means that IP is destroyed at the same time the host is destroyed + - C(absent) Means no public IP at all + version_added: '2.8' + default: absent + enable_ipv6: description: - Enable public IPv6 connectivity on the instance @@ -106,6 +115,13 @@ options: - Time to wait before every attempt to check the state of the server required: false default: 3 + + security_group: + description: + - Security group unique identifier + - If no value provided, the default security group or current security group will be used + required: false + version_added: "2.8" ''' EXAMPLES = ''' @@ -121,6 +137,19 @@ EXAMPLES = ''' - test - www +- name: Create a server attached to a security group + scaleway_compute: + name: foobar + state: present + image: 89ee4018-f8c3-4dc4-a6b5-bca14f985ebe + organization: 951df375-e094-4d26-97c1-ba548eeb9c42 + region: ams1 + commercial_type: VC1S + security_group: 4a31b633-118e-4900-bd52-facf1085fc8d + tags: + - test + - www + - name: Destroy it right after scaleway_compute: name: foobar @@ -141,7 +170,6 @@ from ansible.module_utils.basic import AnsibleModule from ansible.module_utils.six.moves.urllib.parse import quote as urlquote from ansible.module_utils.scaleway import SCALEWAY_LOCATION, scaleway_argument_spec, Scaleway - SCALEWAY_SERVER_STATES = ( 'stopped', 'stopping', @@ -157,6 +185,17 @@ SCALEWAY_TRANSITIONS_STATES = ( ) +def check_image_id(compute_api, image_id): + response = compute_api.get(path="images") + + if response.ok and response.json: + image_ids = [image["id"] for image in response.json["images"]] + if image_id not in image_ids: + compute_api.module.fail_json(msg='Error in getting image %s on %s' % (image_id, compute_api.module.params.get('api_url'))) + else: + compute_api.module.fail_json(msg="Error in getting images from: %s" % compute_api.module.params.get('api_url')) + + def fetch_state(compute_api, server): compute_api.module.debug("fetch_state of server: %s" % server["id"]) response = compute_api.get(path="servers/%s" % server["id"]) @@ -195,17 +234,51 @@ def wait_to_complete_state_transition(compute_api, server): compute_api.module.fail_json(msg="Server takes too long to finish its transition") +def public_ip_payload(compute_api, public_ip): + # We don't want a public ip + if public_ip in ("absent",): + return {"dynamic_ip_required": False} + + # IP is only attached to the instance and is released as soon as the instance terminates + if public_ip in ("dynamic", "allocated"): + return {"dynamic_ip_required": True} + + # We check that the IP we want to attach exists, if so its ID is returned + response = compute_api.get("ips") + if not response.ok: + msg = 'Error during public IP validation: (%s) %s' % (response.status_code, response.json) + compute_api.module.fail_json(msg=msg) + + ip_list = [] + try: + ip_list = response.json["ips"] + except KeyError: + compute_api.module.fail_json(msg="Error in getting the IP information from: %s" % response.json) + + lookup = [ip["id"] for ip in ip_list] + if public_ip in lookup: + return {"public_ip": public_ip} + + def create_server(compute_api, server): compute_api.module.debug("Starting a create_server") target_server = None - response = compute_api.post(path="servers", - data={"enable_ipv6": server["enable_ipv6"], - "boot_type": server["boot_type"], - "tags": server["tags"], - "commercial_type": server["commercial_type"], - "image": server["image"], - "name": server["name"], - "organization": server["organization"]}) + data = {"enable_ipv6": server["enable_ipv6"], + "tags": server["tags"], + "commercial_type": server["commercial_type"], + "image": server["image"], + "dynamic_ip_required": server["dynamic_ip_required"], + "name": server["name"], + "organization": server["organization"] + } + + if server["boot_type"]: + data["boot_type"] = server["boot_type"] + + if server["security_group"]: + data["security_group"] = server["security_group"] + + response = compute_api.post(path="servers", data=data) if not response.ok: msg = 'Error during server creation: (%s) %s' % (response.status_code, response.json) @@ -278,7 +351,7 @@ def present_strategy(compute_api, wished_server): if compute_api.module.check_mode: return changed, {"status": "Server %s attributes would be changed." % target_server["id"]} - server_change_attributes(compute_api=compute_api, target_server=target_server, wished_server=wished_server) + target_server = server_change_attributes(compute_api=compute_api, target_server=target_server, wished_server=wished_server) return changed, target_server @@ -300,7 +373,7 @@ def absent_strategy(compute_api, wished_server): return changed, {"status": "Server %s would be made absent." % target_server["id"]} # A server MUST be stopped to be deleted. - while not fetch_state(compute_api=compute_api, server=target_server) == "stopped": + while fetch_state(compute_api=compute_api, server=target_server) != "stopped": wait_to_complete_state_transition(compute_api=compute_api, server=target_server) response = stop_server(compute_api=compute_api, server=target_server) @@ -341,7 +414,7 @@ def running_strategy(compute_api, wished_server): if compute_api.module.check_mode: return changed, {"status": "Server %s attributes would be changed before running it." % target_server["id"]} - server_change_attributes(compute_api=compute_api, target_server=target_server, wished_server=wished_server) + target_server = server_change_attributes(compute_api=compute_api, target_server=target_server, wished_server=wished_server) current_state = fetch_state(compute_api=compute_api, server=target_server) if current_state not in ("running", "starting"): @@ -385,7 +458,7 @@ def stop_strategy(compute_api, wished_server): return changed, { "status": "Server %s attributes would be changed before stopping it." % target_server["id"]} - server_change_attributes(compute_api=compute_api, target_server=target_server, wished_server=wished_server) + target_server = server_change_attributes(compute_api=compute_api, target_server=target_server, wished_server=wished_server) wait_to_complete_state_transition(compute_api=compute_api, server=target_server) @@ -432,7 +505,7 @@ def restart_strategy(compute_api, wished_server): return changed, { "status": "Server %s attributes would be changed before rebooting it." % target_server["id"]} - server_change_attributes(compute_api=compute_api, target_server=target_server, wished_server=wished_server) + target_server = server_change_attributes(compute_api=compute_api, target_server=target_server, wished_server=wished_server) changed = True if compute_api.module.check_mode: @@ -471,8 +544,8 @@ state_strategy = { def find(compute_api, wished_server, per_page=1): compute_api.module.debug("Getting inside find") # Only the name attribute is accepted in the Compute query API - url = 'servers?name=%s&per_page=%d' % (urlquote(wished_server["name"]), per_page) - response = compute_api.get(url) + response = compute_api.get("servers", params={"name": wished_server["name"], + "per_page": per_page}) if not response.ok: msg = 'Error during server search: (%s) %s' % (response.status_code, response.json) @@ -488,6 +561,7 @@ PATCH_MUTABLE_SERVER_ATTRIBUTES = ( "tags", "name", "dynamic_ip_required", + "security_group", ) @@ -499,29 +573,51 @@ def server_attributes_should_be_changed(compute_api, target_server, wished_serve for x in PATCH_MUTABLE_SERVER_ATTRIBUTES if x in target_server and x in wished_server) compute_api.module.debug("Debug dict %s" % debug_dict) - try: - return any([target_server[x] != wished_server[x] - for x in PATCH_MUTABLE_SERVER_ATTRIBUTES - if x in target_server and x in wished_server]) + for key in PATCH_MUTABLE_SERVER_ATTRIBUTES: + if key in target_server and key in wished_server: + # When you are working with dict, only ID matter as we ask user to put only the resource ID in the playbook + if isinstance(target_server[key], dict) and wished_server[key] and "id" in target_server[key].keys( + ) and target_server[key]["id"] != wished_server[key]: + return True + # Handling other structure compare simply the two objects content + elif not isinstance(target_server[key], dict) and target_server[key] != wished_server[key]: + return True + return False except AttributeError: compute_api.module.fail_json(msg="Error while checking if attributes should be changed") def server_change_attributes(compute_api, target_server, wished_server): compute_api.module.debug("Starting patching server attributes") - patch_payload = dict((x, wished_server[x]) - for x in PATCH_MUTABLE_SERVER_ATTRIBUTES - if x in wished_server and x in target_server) + patch_payload = dict() + + for key in PATCH_MUTABLE_SERVER_ATTRIBUTES: + if key in target_server and key in wished_server: + # When you are working with dict, only ID matter as we ask user to put only the resource ID in the playbook + if isinstance(target_server[key], dict) and "id" in target_server[key] and wished_server[key]: + # Setting all key to current value except ID + key_dict = dict((x, target_server[key][x]) for x in target_server[key].keys() if x != "id") + # Setting ID to the user specified ID + key_dict["id"] = wished_server[key] + patch_payload[key] = key_dict + elif not isinstance(target_server[key], dict): + patch_payload[key] = wished_server[key] + response = compute_api.patch(path="servers/%s" % target_server["id"], data=patch_payload) if not response.ok: msg = 'Error during server attributes patching: (%s) %s' % (response.status_code, response.json) compute_api.module.fail_json(msg=msg) + try: + target_server = response.json["server"] + except KeyError: + compute_api.module.fail_json(msg="Error in getting the server information from: %s" % response.json) + wait_to_complete_state_transition(compute_api=compute_api, server=target_server) - return response + return target_server def core(module): @@ -534,12 +630,19 @@ def core(module): "enable_ipv6": module.params["enable_ipv6"], "boot_type": module.params["boot_type"], "tags": module.params["tags"], - "organization": module.params["organization"] + "organization": module.params["organization"], + "security_group": module.params["security_group"] } module.params['api_url'] = SCALEWAY_LOCATION[region]["api_endpoint"] compute_api = Scaleway(module=module) + check_image_id(compute_api, wished_server["image"]) + + # IP parameters of the wished server depends on the configuration + ip_payload = public_ip_payload(compute_api=compute_api, public_ip=module.params["public_ip"]) + wished_server.update(ip_payload) + changed, summary = state_strategy[wished_server["state"]](compute_api=compute_api, wished_server=wished_server) module.exit_json(changed=changed, msg=summary) @@ -552,13 +655,15 @@ def main(): region=dict(required=True, choices=SCALEWAY_LOCATION.keys()), commercial_type=dict(required=True), enable_ipv6=dict(default=False, type="bool"), - boot_type=dict(default="bootscript"), + boot_type=dict(choices=['bootscript', 'local']), + public_ip=dict(default="absent"), state=dict(choices=state_strategy.keys(), default='present'), tags=dict(type="list", default=[]), organization=dict(required=True), wait=dict(type="bool", default=False), wait_timeout=dict(type="int", default=300), wait_sleep_time=dict(type="int", default=3), + security_group=dict(), )) module = AnsibleModule( argument_spec=argument_spec, diff --git a/roles/cloud-scaleway/tasks/main.yml b/roles/cloud-scaleway/tasks/main.yml index d7aae79..28524b5 100644 --- a/roles/cloud-scaleway/tasks/main.yml +++ b/roles/cloud-scaleway/tasks/main.yml @@ -24,6 +24,7 @@ scaleway_compute: name: "{{ algo_server_name }}" enable_ipv6: true + public_ip: dynamic boot_type: local state: running image: "{{ images[0] }}"