2020-03-09 10:11:07 +01:00
|
|
|
#!/usr/bin/python
|
2021-08-08 10:40:22 +02:00
|
|
|
# -*- coding: utf-8 -*-
|
2020-03-09 10:11:07 +01:00
|
|
|
#
|
|
|
|
# Scaleway Compute management module
|
|
|
|
#
|
|
|
|
# Copyright (C) 2018 Online SAS.
|
|
|
|
# https://www.scaleway.com
|
|
|
|
#
|
2022-08-05 13:17:19 +02:00
|
|
|
# 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
|
2020-03-09 10:11:07 +01:00
|
|
|
|
|
|
|
from __future__ import absolute_import, division, print_function
|
|
|
|
|
|
|
|
__metaclass__ = type
|
|
|
|
|
|
|
|
DOCUMENTATION = '''
|
|
|
|
---
|
|
|
|
module: scaleway_compute
|
|
|
|
short_description: Scaleway compute management module
|
2021-12-08 18:32:26 +01:00
|
|
|
author: Remy Leone (@remyleone)
|
2020-03-09 10:11:07 +01:00
|
|
|
description:
|
|
|
|
- "This module manages compute instances on Scaleway."
|
|
|
|
extends_documentation_fragment:
|
|
|
|
- community.general.scaleway
|
|
|
|
|
|
|
|
|
|
|
|
options:
|
|
|
|
|
|
|
|
public_ip:
|
2020-11-12 08:26:54 +01:00
|
|
|
type: str
|
2020-03-09 10:11:07 +01:00
|
|
|
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
|
|
|
|
default: absent
|
|
|
|
|
|
|
|
enable_ipv6:
|
|
|
|
description:
|
|
|
|
- Enable public IPv6 connectivity on the instance
|
|
|
|
default: false
|
|
|
|
type: bool
|
|
|
|
|
|
|
|
image:
|
2020-11-12 08:26:54 +01:00
|
|
|
type: str
|
2020-03-09 10:11:07 +01:00
|
|
|
description:
|
|
|
|
- Image identifier used to start the instance with
|
|
|
|
required: true
|
|
|
|
|
|
|
|
name:
|
2020-11-12 08:26:54 +01:00
|
|
|
type: str
|
2020-03-09 10:11:07 +01:00
|
|
|
description:
|
|
|
|
- Name of the instance
|
|
|
|
|
|
|
|
organization:
|
2020-11-12 08:26:54 +01:00
|
|
|
type: str
|
2020-03-09 10:11:07 +01:00
|
|
|
description:
|
2021-12-28 15:03:14 +01:00
|
|
|
- Organization identifier.
|
|
|
|
- Exactly one of I(project) and I(organization) must be specified.
|
|
|
|
|
|
|
|
project:
|
|
|
|
type: str
|
|
|
|
description:
|
|
|
|
- Project identifier.
|
|
|
|
- Exactly one of I(project) and I(organization) must be specified.
|
|
|
|
version_added: 4.3.0
|
2020-03-09 10:11:07 +01:00
|
|
|
|
|
|
|
state:
|
2020-11-12 08:26:54 +01:00
|
|
|
type: str
|
2020-03-09 10:11:07 +01:00
|
|
|
description:
|
|
|
|
- Indicate desired state of the instance.
|
|
|
|
default: present
|
|
|
|
choices:
|
|
|
|
- present
|
|
|
|
- absent
|
|
|
|
- running
|
|
|
|
- restarted
|
|
|
|
- stopped
|
|
|
|
|
|
|
|
tags:
|
2020-11-12 08:26:54 +01:00
|
|
|
type: list
|
2021-03-08 08:35:09 +01:00
|
|
|
elements: str
|
2020-03-09 10:11:07 +01:00
|
|
|
description:
|
|
|
|
- List of tags to apply to the instance (5 max)
|
|
|
|
required: false
|
|
|
|
default: []
|
|
|
|
|
|
|
|
region:
|
2020-11-12 08:26:54 +01:00
|
|
|
type: str
|
2020-03-09 10:11:07 +01:00
|
|
|
description:
|
|
|
|
- Scaleway compute zone
|
|
|
|
required: true
|
|
|
|
choices:
|
|
|
|
- ams1
|
|
|
|
- EMEA-NL-EVS
|
|
|
|
- par1
|
|
|
|
- EMEA-FR-PAR1
|
2021-01-28 12:51:07 +01:00
|
|
|
- par2
|
|
|
|
- EMEA-FR-PAR2
|
|
|
|
- waw1
|
|
|
|
- EMEA-PL-WAW1
|
2020-03-09 10:11:07 +01:00
|
|
|
|
|
|
|
commercial_type:
|
2020-11-12 08:26:54 +01:00
|
|
|
type: str
|
2020-03-09 10:11:07 +01:00
|
|
|
description:
|
|
|
|
- Commercial name of the compute node
|
|
|
|
required: true
|
|
|
|
|
|
|
|
wait:
|
|
|
|
description:
|
|
|
|
- Wait for the instance to reach its desired state before returning.
|
|
|
|
type: bool
|
2022-08-24 20:22:10 +02:00
|
|
|
default: false
|
2020-03-09 10:11:07 +01:00
|
|
|
|
|
|
|
wait_timeout:
|
2020-11-12 08:26:54 +01:00
|
|
|
type: int
|
2020-03-09 10:11:07 +01:00
|
|
|
description:
|
|
|
|
- Time to wait for the server to reach the expected state
|
|
|
|
required: false
|
|
|
|
default: 300
|
|
|
|
|
|
|
|
wait_sleep_time:
|
2020-11-12 08:26:54 +01:00
|
|
|
type: int
|
2020-03-09 10:11:07 +01:00
|
|
|
description:
|
|
|
|
- Time to wait before every attempt to check the state of the server
|
|
|
|
required: false
|
|
|
|
default: 3
|
|
|
|
|
|
|
|
security_group:
|
2020-11-12 08:26:54 +01:00
|
|
|
type: str
|
2020-03-09 10:11:07 +01:00
|
|
|
description:
|
|
|
|
- Security group unique identifier
|
|
|
|
- If no value provided, the default security group or current security group will be used
|
|
|
|
required: false
|
|
|
|
'''
|
|
|
|
|
|
|
|
EXAMPLES = '''
|
|
|
|
- name: Create a server
|
2020-07-13 21:50:31 +02:00
|
|
|
community.general.scaleway_compute:
|
2020-03-09 10:11:07 +01:00
|
|
|
name: foobar
|
|
|
|
state: present
|
|
|
|
image: 89ee4018-f8c3-4dc4-a6b5-bca14f985ebe
|
2021-12-28 15:03:14 +01:00
|
|
|
project: 951df375-e094-4d26-97c1-ba548eeb9c42
|
2020-03-09 10:11:07 +01:00
|
|
|
region: ams1
|
|
|
|
commercial_type: VC1S
|
|
|
|
tags:
|
|
|
|
- test
|
|
|
|
- www
|
|
|
|
|
|
|
|
- name: Create a server attached to a security group
|
2020-07-13 21:50:31 +02:00
|
|
|
community.general.scaleway_compute:
|
2020-03-09 10:11:07 +01:00
|
|
|
name: foobar
|
|
|
|
state: present
|
|
|
|
image: 89ee4018-f8c3-4dc4-a6b5-bca14f985ebe
|
2021-12-28 15:03:14 +01:00
|
|
|
project: 951df375-e094-4d26-97c1-ba548eeb9c42
|
2020-03-09 10:11:07 +01:00
|
|
|
region: ams1
|
|
|
|
commercial_type: VC1S
|
|
|
|
security_group: 4a31b633-118e-4900-bd52-facf1085fc8d
|
|
|
|
tags:
|
|
|
|
- test
|
|
|
|
- www
|
|
|
|
|
|
|
|
- name: Destroy it right after
|
2020-07-13 21:50:31 +02:00
|
|
|
community.general.scaleway_compute:
|
2020-03-09 10:11:07 +01:00
|
|
|
name: foobar
|
|
|
|
state: absent
|
|
|
|
image: 89ee4018-f8c3-4dc4-a6b5-bca14f985ebe
|
2021-12-28 15:03:14 +01:00
|
|
|
project: 951df375-e094-4d26-97c1-ba548eeb9c42
|
2020-03-09 10:11:07 +01:00
|
|
|
region: ams1
|
|
|
|
commercial_type: VC1S
|
|
|
|
'''
|
|
|
|
|
|
|
|
RETURN = '''
|
|
|
|
'''
|
|
|
|
|
|
|
|
import datetime
|
|
|
|
import time
|
|
|
|
|
|
|
|
from ansible.module_utils.basic import AnsibleModule
|
|
|
|
from ansible_collections.community.general.plugins.module_utils.scaleway import SCALEWAY_LOCATION, scaleway_argument_spec, Scaleway
|
|
|
|
|
|
|
|
SCALEWAY_SERVER_STATES = (
|
|
|
|
'stopped',
|
|
|
|
'stopping',
|
|
|
|
'starting',
|
|
|
|
'running',
|
|
|
|
'locked'
|
|
|
|
)
|
|
|
|
|
|
|
|
SCALEWAY_TRANSITIONS_STATES = (
|
|
|
|
"stopping",
|
|
|
|
"starting",
|
|
|
|
"pending"
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
def check_image_id(compute_api, image_id):
|
|
|
|
response = compute_api.get(path="images/%s" % image_id)
|
|
|
|
|
|
|
|
if not response.ok:
|
|
|
|
msg = 'Error in getting image %s on %s : %s' % (image_id, compute_api.module.params.get('api_url'), response.json)
|
|
|
|
compute_api.module.fail_json(msg=msg)
|
|
|
|
|
|
|
|
|
|
|
|
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"])
|
|
|
|
|
|
|
|
if response.status_code == 404:
|
|
|
|
return "absent"
|
|
|
|
|
|
|
|
if not response.ok:
|
|
|
|
msg = 'Error during state fetching: (%s) %s' % (response.status_code, response.json)
|
|
|
|
compute_api.module.fail_json(msg=msg)
|
|
|
|
|
|
|
|
try:
|
|
|
|
compute_api.module.debug("Server %s in state: %s" % (server["id"], response.json["server"]["state"]))
|
|
|
|
return response.json["server"]["state"]
|
|
|
|
except KeyError:
|
|
|
|
compute_api.module.fail_json(msg="Could not fetch state in %s" % response.json)
|
|
|
|
|
|
|
|
|
scaleway_compute: When removing node, wait for transition (#444)
To remove a scaleway compute node, one needs to stop it first. This is
handled internally within the module by shutting down before removing.
Shutting down the node transitions it to a "stopping" state, which is
not the "stopped" state we expect. We thus need the transition to
complete so that we can put it in the actual target state (absent, i.e.
delete it).
The mechanism for waiting for such transitions today is controlled by
module parameters, with default to not being enabled at all, which
includes the transition from ([running] -(stopping)-> [stopped]).
Without this chage, in case of a running node, we would shut it down
(transition it to "stopping"), not wait for it complete the transition,
realize that it's not yet stopped and issue a second shut down command
to the api. This would fail with a 400 Bad Request error, "already
stopped".
Reference: https://github.com/ansible/ansible/issues/45740
Reported-by: zwindler
2020-06-11 10:34:15 +02:00
|
|
|
def wait_to_complete_state_transition(compute_api, server, wait=None):
|
|
|
|
if wait is None:
|
|
|
|
wait = compute_api.module.params["wait"]
|
2020-03-09 10:11:07 +01:00
|
|
|
if not wait:
|
|
|
|
return
|
scaleway_compute: When removing node, wait for transition (#444)
To remove a scaleway compute node, one needs to stop it first. This is
handled internally within the module by shutting down before removing.
Shutting down the node transitions it to a "stopping" state, which is
not the "stopped" state we expect. We thus need the transition to
complete so that we can put it in the actual target state (absent, i.e.
delete it).
The mechanism for waiting for such transitions today is controlled by
module parameters, with default to not being enabled at all, which
includes the transition from ([running] -(stopping)-> [stopped]).
Without this chage, in case of a running node, we would shut it down
(transition it to "stopping"), not wait for it complete the transition,
realize that it's not yet stopped and issue a second shut down command
to the api. This would fail with a 400 Bad Request error, "already
stopped".
Reference: https://github.com/ansible/ansible/issues/45740
Reported-by: zwindler
2020-06-11 10:34:15 +02:00
|
|
|
|
2020-03-09 10:11:07 +01:00
|
|
|
wait_timeout = compute_api.module.params["wait_timeout"]
|
|
|
|
wait_sleep_time = compute_api.module.params["wait_sleep_time"]
|
|
|
|
|
|
|
|
start = datetime.datetime.utcnow()
|
|
|
|
end = start + datetime.timedelta(seconds=wait_timeout)
|
|
|
|
while datetime.datetime.utcnow() < end:
|
|
|
|
compute_api.module.debug("We are going to wait for the server to finish its transition")
|
|
|
|
if fetch_state(compute_api, server) not in SCALEWAY_TRANSITIONS_STATES:
|
|
|
|
compute_api.module.debug("It seems that the server is not in transition anymore.")
|
|
|
|
compute_api.module.debug("Server in state: %s" % fetch_state(compute_api, server))
|
|
|
|
break
|
|
|
|
time.sleep(wait_sleep_time)
|
|
|
|
else:
|
|
|
|
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
|
|
|
|
data = {"enable_ipv6": server["enable_ipv6"],
|
|
|
|
"tags": server["tags"],
|
|
|
|
"commercial_type": server["commercial_type"],
|
|
|
|
"image": server["image"],
|
|
|
|
"dynamic_ip_required": server["dynamic_ip_required"],
|
2021-12-28 15:03:14 +01:00
|
|
|
"name": server["name"]
|
2020-03-09 10:11:07 +01:00
|
|
|
}
|
|
|
|
|
2021-12-28 15:03:14 +01:00
|
|
|
if server["project"]:
|
|
|
|
data["project"] = server["project"]
|
|
|
|
|
|
|
|
if server["organization"]:
|
|
|
|
data["organization"] = server["organization"]
|
|
|
|
|
2020-03-09 10:11:07 +01:00
|
|
|
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)
|
|
|
|
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 target_server
|
|
|
|
|
|
|
|
|
|
|
|
def restart_server(compute_api, server):
|
|
|
|
return perform_action(compute_api=compute_api, server=server, action="reboot")
|
|
|
|
|
|
|
|
|
|
|
|
def stop_server(compute_api, server):
|
|
|
|
return perform_action(compute_api=compute_api, server=server, action="poweroff")
|
|
|
|
|
|
|
|
|
|
|
|
def start_server(compute_api, server):
|
|
|
|
return perform_action(compute_api=compute_api, server=server, action="poweron")
|
|
|
|
|
|
|
|
|
|
|
|
def perform_action(compute_api, server, action):
|
|
|
|
response = compute_api.post(path="servers/%s/action" % server["id"],
|
|
|
|
data={"action": action})
|
|
|
|
if not response.ok:
|
|
|
|
msg = 'Error during server %s: (%s) %s' % (action, response.status_code, response.json)
|
|
|
|
compute_api.module.fail_json(msg=msg)
|
|
|
|
|
|
|
|
wait_to_complete_state_transition(compute_api=compute_api, server=server)
|
|
|
|
|
|
|
|
return response
|
|
|
|
|
|
|
|
|
|
|
|
def remove_server(compute_api, server):
|
|
|
|
compute_api.module.debug("Starting remove server strategy")
|
|
|
|
response = compute_api.delete(path="servers/%s" % server["id"])
|
|
|
|
if not response.ok:
|
|
|
|
msg = 'Error during server deletion: (%s) %s' % (response.status_code, response.json)
|
|
|
|
compute_api.module.fail_json(msg=msg)
|
|
|
|
|
|
|
|
wait_to_complete_state_transition(compute_api=compute_api, server=server)
|
|
|
|
|
|
|
|
return response
|
|
|
|
|
|
|
|
|
|
|
|
def present_strategy(compute_api, wished_server):
|
|
|
|
compute_api.module.debug("Starting present strategy")
|
|
|
|
changed = False
|
|
|
|
query_results = find(compute_api=compute_api, wished_server=wished_server, per_page=1)
|
|
|
|
|
|
|
|
if not query_results:
|
|
|
|
changed = True
|
|
|
|
if compute_api.module.check_mode:
|
|
|
|
return changed, {"status": "A server would be created."}
|
|
|
|
|
|
|
|
target_server = create_server(compute_api=compute_api, server=wished_server)
|
|
|
|
else:
|
|
|
|
target_server = query_results[0]
|
|
|
|
|
|
|
|
if server_attributes_should_be_changed(compute_api=compute_api, target_server=target_server,
|
|
|
|
wished_server=wished_server):
|
|
|
|
changed = True
|
|
|
|
|
|
|
|
if compute_api.module.check_mode:
|
|
|
|
return changed, {"status": "Server %s attributes would be changed." % target_server["id"]}
|
|
|
|
|
|
|
|
target_server = server_change_attributes(compute_api=compute_api, target_server=target_server, wished_server=wished_server)
|
|
|
|
|
|
|
|
return changed, target_server
|
|
|
|
|
|
|
|
|
|
|
|
def absent_strategy(compute_api, wished_server):
|
|
|
|
compute_api.module.debug("Starting absent strategy")
|
|
|
|
changed = False
|
|
|
|
target_server = None
|
|
|
|
query_results = find(compute_api=compute_api, wished_server=wished_server, per_page=1)
|
|
|
|
|
|
|
|
if not query_results:
|
|
|
|
return changed, {"status": "Server already absent."}
|
|
|
|
else:
|
|
|
|
target_server = query_results[0]
|
|
|
|
|
|
|
|
changed = True
|
|
|
|
|
|
|
|
if compute_api.module.check_mode:
|
|
|
|
return changed, {"status": "Server %s would be made absent." % target_server["id"]}
|
|
|
|
|
|
|
|
# A server MUST be stopped to be deleted.
|
|
|
|
while fetch_state(compute_api=compute_api, server=target_server) != "stopped":
|
scaleway_compute: When removing node, wait for transition (#444)
To remove a scaleway compute node, one needs to stop it first. This is
handled internally within the module by shutting down before removing.
Shutting down the node transitions it to a "stopping" state, which is
not the "stopped" state we expect. We thus need the transition to
complete so that we can put it in the actual target state (absent, i.e.
delete it).
The mechanism for waiting for such transitions today is controlled by
module parameters, with default to not being enabled at all, which
includes the transition from ([running] -(stopping)-> [stopped]).
Without this chage, in case of a running node, we would shut it down
(transition it to "stopping"), not wait for it complete the transition,
realize that it's not yet stopped and issue a second shut down command
to the api. This would fail with a 400 Bad Request error, "already
stopped".
Reference: https://github.com/ansible/ansible/issues/45740
Reported-by: zwindler
2020-06-11 10:34:15 +02:00
|
|
|
wait_to_complete_state_transition(compute_api=compute_api, server=target_server, wait=True)
|
2020-03-09 10:11:07 +01:00
|
|
|
response = stop_server(compute_api=compute_api, server=target_server)
|
|
|
|
|
|
|
|
if not response.ok:
|
|
|
|
err_msg = 'Error while stopping a server before removing it [{0}: {1}]'.format(response.status_code,
|
|
|
|
response.json)
|
|
|
|
compute_api.module.fail_json(msg=err_msg)
|
|
|
|
|
scaleway_compute: When removing node, wait for transition (#444)
To remove a scaleway compute node, one needs to stop it first. This is
handled internally within the module by shutting down before removing.
Shutting down the node transitions it to a "stopping" state, which is
not the "stopped" state we expect. We thus need the transition to
complete so that we can put it in the actual target state (absent, i.e.
delete it).
The mechanism for waiting for such transitions today is controlled by
module parameters, with default to not being enabled at all, which
includes the transition from ([running] -(stopping)-> [stopped]).
Without this chage, in case of a running node, we would shut it down
(transition it to "stopping"), not wait for it complete the transition,
realize that it's not yet stopped and issue a second shut down command
to the api. This would fail with a 400 Bad Request error, "already
stopped".
Reference: https://github.com/ansible/ansible/issues/45740
Reported-by: zwindler
2020-06-11 10:34:15 +02:00
|
|
|
wait_to_complete_state_transition(compute_api=compute_api, server=target_server, wait=True)
|
2020-03-09 10:11:07 +01:00
|
|
|
|
|
|
|
response = remove_server(compute_api=compute_api, server=target_server)
|
|
|
|
|
|
|
|
if not response.ok:
|
|
|
|
err_msg = 'Error while removing server [{0}: {1}]'.format(response.status_code, response.json)
|
|
|
|
compute_api.module.fail_json(msg=err_msg)
|
|
|
|
|
|
|
|
return changed, {"status": "Server %s deleted" % target_server["id"]}
|
|
|
|
|
|
|
|
|
|
|
|
def running_strategy(compute_api, wished_server):
|
|
|
|
compute_api.module.debug("Starting running strategy")
|
|
|
|
changed = False
|
|
|
|
query_results = find(compute_api=compute_api, wished_server=wished_server, per_page=1)
|
|
|
|
|
|
|
|
if not query_results:
|
|
|
|
changed = True
|
|
|
|
if compute_api.module.check_mode:
|
|
|
|
return changed, {"status": "A server would be created before being run."}
|
|
|
|
|
|
|
|
target_server = create_server(compute_api=compute_api, server=wished_server)
|
|
|
|
else:
|
|
|
|
target_server = query_results[0]
|
|
|
|
|
|
|
|
if server_attributes_should_be_changed(compute_api=compute_api, target_server=target_server,
|
|
|
|
wished_server=wished_server):
|
|
|
|
changed = True
|
|
|
|
|
|
|
|
if compute_api.module.check_mode:
|
|
|
|
return changed, {"status": "Server %s attributes would be changed before running it." % target_server["id"]}
|
|
|
|
|
|
|
|
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"):
|
|
|
|
compute_api.module.debug("running_strategy: Server in state: %s" % current_state)
|
|
|
|
changed = True
|
|
|
|
|
|
|
|
if compute_api.module.check_mode:
|
|
|
|
return changed, {"status": "Server %s attributes would be changed." % target_server["id"]}
|
|
|
|
|
|
|
|
response = start_server(compute_api=compute_api, server=target_server)
|
|
|
|
if not response.ok:
|
|
|
|
msg = 'Error while running server [{0}: {1}]'.format(response.status_code, response.json)
|
|
|
|
compute_api.module.fail_json(msg=msg)
|
|
|
|
|
|
|
|
return changed, target_server
|
|
|
|
|
|
|
|
|
|
|
|
def stop_strategy(compute_api, wished_server):
|
|
|
|
compute_api.module.debug("Starting stop strategy")
|
|
|
|
query_results = find(compute_api=compute_api, wished_server=wished_server, per_page=1)
|
|
|
|
|
|
|
|
changed = False
|
|
|
|
|
|
|
|
if not query_results:
|
|
|
|
|
|
|
|
if compute_api.module.check_mode:
|
|
|
|
return changed, {"status": "A server would be created before being stopped."}
|
|
|
|
|
|
|
|
target_server = create_server(compute_api=compute_api, server=wished_server)
|
|
|
|
changed = True
|
|
|
|
else:
|
|
|
|
target_server = query_results[0]
|
|
|
|
|
|
|
|
compute_api.module.debug("stop_strategy: Servers are found.")
|
|
|
|
|
|
|
|
if server_attributes_should_be_changed(compute_api=compute_api, target_server=target_server,
|
|
|
|
wished_server=wished_server):
|
|
|
|
changed = True
|
|
|
|
|
|
|
|
if compute_api.module.check_mode:
|
|
|
|
return changed, {
|
|
|
|
"status": "Server %s attributes would be changed before stopping it." % target_server["id"]}
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
|
|
|
current_state = fetch_state(compute_api=compute_api, server=target_server)
|
|
|
|
if current_state not in ("stopped",):
|
|
|
|
compute_api.module.debug("stop_strategy: Server in state: %s" % current_state)
|
|
|
|
|
|
|
|
changed = True
|
|
|
|
|
|
|
|
if compute_api.module.check_mode:
|
|
|
|
return changed, {"status": "Server %s would be stopped." % target_server["id"]}
|
|
|
|
|
|
|
|
response = stop_server(compute_api=compute_api, server=target_server)
|
|
|
|
compute_api.module.debug(response.json)
|
|
|
|
compute_api.module.debug(response.ok)
|
|
|
|
|
|
|
|
if not response.ok:
|
|
|
|
msg = 'Error while stopping server [{0}: {1}]'.format(response.status_code, response.json)
|
|
|
|
compute_api.module.fail_json(msg=msg)
|
|
|
|
|
|
|
|
return changed, target_server
|
|
|
|
|
|
|
|
|
|
|
|
def restart_strategy(compute_api, wished_server):
|
|
|
|
compute_api.module.debug("Starting restart strategy")
|
|
|
|
changed = False
|
|
|
|
query_results = find(compute_api=compute_api, wished_server=wished_server, per_page=1)
|
|
|
|
|
|
|
|
if not query_results:
|
|
|
|
changed = True
|
|
|
|
if compute_api.module.check_mode:
|
|
|
|
return changed, {"status": "A server would be created before being rebooted."}
|
|
|
|
|
|
|
|
target_server = create_server(compute_api=compute_api, server=wished_server)
|
|
|
|
else:
|
|
|
|
target_server = query_results[0]
|
|
|
|
|
|
|
|
if server_attributes_should_be_changed(compute_api=compute_api,
|
|
|
|
target_server=target_server,
|
|
|
|
wished_server=wished_server):
|
|
|
|
changed = True
|
|
|
|
|
|
|
|
if compute_api.module.check_mode:
|
|
|
|
return changed, {
|
|
|
|
"status": "Server %s attributes would be changed before rebooting it." % target_server["id"]}
|
|
|
|
|
|
|
|
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:
|
|
|
|
return changed, {"status": "Server %s would be rebooted." % target_server["id"]}
|
|
|
|
|
|
|
|
wait_to_complete_state_transition(compute_api=compute_api, server=target_server)
|
|
|
|
|
|
|
|
if fetch_state(compute_api=compute_api, server=target_server) in ("running",):
|
|
|
|
response = restart_server(compute_api=compute_api, server=target_server)
|
|
|
|
wait_to_complete_state_transition(compute_api=compute_api, server=target_server)
|
|
|
|
if not response.ok:
|
|
|
|
msg = 'Error while restarting server that was running [{0}: {1}].'.format(response.status_code,
|
|
|
|
response.json)
|
|
|
|
compute_api.module.fail_json(msg=msg)
|
|
|
|
|
|
|
|
if fetch_state(compute_api=compute_api, server=target_server) in ("stopped",):
|
|
|
|
response = restart_server(compute_api=compute_api, server=target_server)
|
|
|
|
wait_to_complete_state_transition(compute_api=compute_api, server=target_server)
|
|
|
|
if not response.ok:
|
|
|
|
msg = 'Error while restarting server that was stopped [{0}: {1}].'.format(response.status_code,
|
|
|
|
response.json)
|
|
|
|
compute_api.module.fail_json(msg=msg)
|
|
|
|
|
|
|
|
return changed, target_server
|
|
|
|
|
|
|
|
|
|
|
|
state_strategy = {
|
|
|
|
"present": present_strategy,
|
|
|
|
"restarted": restart_strategy,
|
|
|
|
"stopped": stop_strategy,
|
|
|
|
"running": running_strategy,
|
|
|
|
"absent": absent_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
|
|
|
|
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)
|
|
|
|
compute_api.module.fail_json(msg=msg)
|
|
|
|
|
|
|
|
search_results = response.json["servers"]
|
|
|
|
|
|
|
|
return search_results
|
|
|
|
|
|
|
|
|
|
|
|
PATCH_MUTABLE_SERVER_ATTRIBUTES = (
|
|
|
|
"ipv6",
|
|
|
|
"tags",
|
|
|
|
"name",
|
|
|
|
"dynamic_ip_required",
|
|
|
|
"security_group",
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
def server_attributes_should_be_changed(compute_api, target_server, wished_server):
|
|
|
|
compute_api.module.debug("Checking if server attributes should be changed")
|
|
|
|
compute_api.module.debug("Current Server: %s" % target_server)
|
|
|
|
compute_api.module.debug("Wished Server: %s" % wished_server)
|
|
|
|
debug_dict = dict((x, (target_server[x], wished_server[x]))
|
|
|
|
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:
|
|
|
|
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()
|
|
|
|
|
|
|
|
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 target_server
|
|
|
|
|
|
|
|
|
|
|
|
def core(module):
|
|
|
|
region = module.params["region"]
|
|
|
|
wished_server = {
|
|
|
|
"state": module.params["state"],
|
|
|
|
"image": module.params["image"],
|
|
|
|
"name": module.params["name"],
|
|
|
|
"commercial_type": module.params["commercial_type"],
|
|
|
|
"enable_ipv6": module.params["enable_ipv6"],
|
|
|
|
"tags": module.params["tags"],
|
|
|
|
"organization": module.params["organization"],
|
2021-12-28 15:03:14 +01:00
|
|
|
"project": module.params["project"],
|
2020-03-09 10:11:07 +01:00
|
|
|
"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)
|
|
|
|
|
|
|
|
|
|
|
|
def main():
|
|
|
|
argument_spec = scaleway_argument_spec()
|
|
|
|
argument_spec.update(dict(
|
|
|
|
image=dict(required=True),
|
|
|
|
name=dict(),
|
2020-11-12 08:26:54 +01:00
|
|
|
region=dict(required=True, choices=list(SCALEWAY_LOCATION.keys())),
|
2020-03-09 10:11:07 +01:00
|
|
|
commercial_type=dict(required=True),
|
|
|
|
enable_ipv6=dict(default=False, type="bool"),
|
|
|
|
public_ip=dict(default="absent"),
|
2020-11-12 08:26:54 +01:00
|
|
|
state=dict(choices=list(state_strategy.keys()), default='present'),
|
2021-03-08 08:35:09 +01:00
|
|
|
tags=dict(type="list", elements="str", default=[]),
|
2021-12-28 15:03:14 +01:00
|
|
|
organization=dict(),
|
|
|
|
project=dict(),
|
2020-03-09 10:11:07 +01:00
|
|
|
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,
|
|
|
|
supports_check_mode=True,
|
2021-12-28 15:03:14 +01:00
|
|
|
mutually_exclusive=[
|
|
|
|
('organization', 'project'),
|
|
|
|
],
|
|
|
|
required_one_of=[
|
|
|
|
('organization', 'project'),
|
|
|
|
],
|
2020-03-09 10:11:07 +01:00
|
|
|
)
|
|
|
|
|
|
|
|
core(module)
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == '__main__':
|
|
|
|
main()
|