Mini Shell
"""
Scaleway Cloud Module
=====================
.. versionadded:: 2015.8.0
The Scaleway cloud module is used to interact with your Scaleway BareMetal
Servers.
Use of this module only requires the ``api_key`` parameter to be set. Set up
the cloud configuration at ``/etc/salt/cloud.providers`` or
``/etc/salt/cloud.providers.d/scaleway.conf``:
.. code-block:: yaml
scaleway-config:
# Scaleway organization and token
access_key: 0e604a2c-aea6-4081-acb2-e1d1258ef95c
token: be8fd96b-04eb-4d39-b6ba-a9edbcf17f12
driver: scaleway
"""
import logging
import os
import pprint
import time
import salt.config as config
import salt.utils.cloud
import salt.utils.json
from salt.exceptions import (
SaltCloudConfigError,
SaltCloudExecutionFailure,
SaltCloudExecutionTimeout,
SaltCloudNotFound,
SaltCloudSystemExit,
)
log = logging.getLogger(__name__)
__virtualname__ = "scaleway"
# Only load in this module if the Scaleway configurations are in place
def __virtual__():
"""
Check for Scaleway configurations.
"""
if get_configured_provider() is False:
return False
return __virtualname__
def _get_active_provider_name():
try:
return __active_provider_name__.value()
except AttributeError:
return __active_provider_name__
def get_configured_provider():
"""Return the first configured instance."""
return config.is_provider_configured(
__opts__, _get_active_provider_name() or __virtualname__, ("token",)
)
def avail_images(call=None):
"""Return a list of the images that are on the provider."""
if call == "action":
raise SaltCloudSystemExit(
"The avail_images function must be called with "
"-f or --function, or with the --list-images option"
)
items = query(method="images", root="marketplace_root")
ret = {}
for image in items["images"]:
ret[image["id"]] = {}
for item in image:
ret[image["id"]][item] = str(image[item])
return ret
def list_nodes(call=None):
"""Return a list of the BareMetal servers that are on the provider."""
if call == "action":
raise SaltCloudSystemExit(
"The list_nodes function must be called with -f or --function."
)
items = query(method="servers")
ret = {}
for node in items["servers"]:
public_ips = []
private_ips = []
image_id = ""
if node.get("public_ip"):
public_ips = [node["public_ip"]["address"]]
if node.get("private_ip"):
private_ips = [node["private_ip"]]
if node.get("image"):
image_id = node["image"]["id"]
ret[node["name"]] = {
"id": node["id"],
"image_id": image_id,
"public_ips": public_ips,
"private_ips": private_ips,
"size": node["volumes"]["0"]["size"],
"state": node["state"],
}
return ret
def list_nodes_full(call=None):
"""Return a list of the BareMetal servers that are on the provider."""
if call == "action":
raise SaltCloudSystemExit(
"list_nodes_full must be called with -f or --function"
)
items = query(method="servers")
# For each server, iterate on its parameters.
ret = {}
for node in items["servers"]:
ret[node["name"]] = {}
for item in node:
value = node[item]
ret[node["name"]][item] = value
return ret
def list_nodes_select(call=None):
"""Return a list of the BareMetal servers that are on the provider, with
select fields.
"""
return salt.utils.cloud.list_nodes_select(
list_nodes_full("function"),
__opts__["query.selection"],
call,
)
def get_image(server_):
"""Return the image object to use."""
images = avail_images()
server_image = str(
config.get_cloud_config_value("image", server_, __opts__, search_global=False)
)
for image in images:
if server_image in (images[image]["name"], images[image]["id"]):
return images[image]["id"]
raise SaltCloudNotFound(
f"The specified image, '{server_image}', could not be found."
)
def create_node(args):
"""Create a node."""
node = query(method="servers", args=args, http_method="POST")
action = query(
method="servers",
server_id=node["server"]["id"],
command="action",
args={"action": "poweron"},
http_method="POST",
)
return node
def create(server_):
"""
Create a single BareMetal server from a data dict.
"""
try:
# Check for required profile parameters before sending any API calls.
if (
server_["profile"]
and config.is_profile_configured(
__opts__,
_get_active_provider_name() or "scaleway",
server_["profile"],
vm_=server_,
)
is False
):
return False
except AttributeError:
pass
__utils__["cloud.fire_event"](
"event",
"starting create",
"salt/cloud/{}/creating".format(server_["name"]),
args=__utils__["cloud.filter_event"](
"creating", server_, ["name", "profile", "provider", "driver"]
),
sock_dir=__opts__["sock_dir"],
transport=__opts__["transport"],
)
log.info("Creating a BareMetal server %s", server_["name"])
access_key = config.get_cloud_config_value(
"access_key", get_configured_provider(), __opts__, search_global=False
)
commercial_type = config.get_cloud_config_value(
"commercial_type", server_, __opts__, default="C1"
)
key_filename = config.get_cloud_config_value(
"ssh_key_file", server_, __opts__, search_global=False, default=None
)
if key_filename is not None and not os.path.isfile(key_filename):
raise SaltCloudConfigError(
f"The defined key_filename '{key_filename}' does not exist"
)
ssh_password = config.get_cloud_config_value("ssh_password", server_, __opts__)
kwargs = {
"name": server_["name"],
"organization": access_key,
"image": get_image(server_),
"commercial_type": commercial_type,
}
__utils__["cloud.fire_event"](
"event",
"requesting instance",
"salt/cloud/{}/requesting".format(server_["name"]),
args={
"kwargs": __utils__["cloud.filter_event"](
"requesting", kwargs, list(kwargs)
),
},
sock_dir=__opts__["sock_dir"],
transport=__opts__["transport"],
)
try:
ret = create_node(kwargs)
except Exception as exc: # pylint: disable=broad-except
log.error(
"Error creating %s on Scaleway\n\n"
"The following exception was thrown when trying to "
"run the initial deployment: %s",
server_["name"],
exc,
# Show the traceback if the debug logging level is enabled
exc_info_on_loglevel=logging.DEBUG,
)
return False
def __query_node_data(server_name):
"""Called to check if the server has a public IP address."""
data = show_instance(server_name, "action")
if data and data.get("public_ip"):
return data
return False
try:
data = salt.utils.cloud.wait_for_ip(
__query_node_data,
update_args=(server_["name"],),
timeout=config.get_cloud_config_value(
"wait_for_ip_timeout", server_, __opts__, default=10 * 60
),
interval=config.get_cloud_config_value(
"wait_for_ip_interval", server_, __opts__, default=10
),
)
except (SaltCloudExecutionTimeout, SaltCloudExecutionFailure) as exc:
try:
# It might be already up, let's destroy it!
destroy(server_["name"])
except SaltCloudSystemExit:
pass
finally:
raise SaltCloudSystemExit(str(exc))
server_["ssh_host"] = data["public_ip"]["address"]
server_["ssh_password"] = ssh_password
server_["key_filename"] = key_filename
ret = __utils__["cloud.bootstrap"](server_, __opts__)
ret.update(data)
log.info("Created BareMetal server '%s'", server_["name"])
log.debug(
"'%s' BareMetal server creation details:\n%s",
server_["name"],
pprint.pformat(data),
)
__utils__["cloud.fire_event"](
"event",
"created instance",
"salt/cloud/{}/created".format(server_["name"]),
args=__utils__["cloud.filter_event"](
"created", server_, ["name", "profile", "provider", "driver"]
),
sock_dir=__opts__["sock_dir"],
transport=__opts__["transport"],
)
return ret
def query(
method="servers",
server_id=None,
command=None,
args=None,
http_method="GET",
root="api_root",
):
"""Make a call to the Scaleway API."""
if root == "api_root":
default_url = "https://cp-par1.scaleway.com"
else:
default_url = "https://api-marketplace.scaleway.com"
vm_ = get_configured_provider()
base_path = str(
config.get_cloud_config_value(
root,
vm_,
__opts__,
search_global=False,
default=default_url,
)
)
path = f"{base_path}/{method}/"
if server_id:
path += f"{server_id}/"
if command:
path += command
if not isinstance(args, dict):
args = {}
token = config.get_cloud_config_value("token", vm_, __opts__, search_global=False)
data = salt.utils.json.dumps(args)
request = __utils__["http.query"](
path,
method=http_method,
data=data,
headers={
"X-Auth-Token": token,
"User-Agent": "salt-cloud",
"Content-Type": "application/json",
},
)
if request.status_code > 299:
raise SaltCloudSystemExit(
"An error occurred while querying Scaleway. HTTP Code: {} "
"Error: '{}'".format(request.status_code, request.text)
)
# success without data
if request["status"] == 204:
return True
return salt.utils.json.loads(request["body"])
def script(server_):
"""Return the script deployment object."""
return salt.utils.cloud.os_script(
config.get_cloud_config_value("script", server_, __opts__),
server_,
__opts__,
salt.utils.cloud.salt_config_to_yaml(
salt.utils.cloud.minion_config(__opts__, server_)
),
)
def show_instance(name, call=None):
"""Show the details from a Scaleway BareMetal server."""
if call != "action":
raise SaltCloudSystemExit(
"The show_instance action must be called with -a or --action."
)
node = _get_node(name)
__utils__["cloud.cache_node"](node, _get_active_provider_name(), __opts__)
return node
def _get_node(name):
for attempt in reversed(list(range(10))):
try:
return list_nodes_full()[name]
except KeyError:
log.debug(
"Failed to get the data for node '%s'. Remaining attempts: %s",
name,
attempt,
)
# Just a little delay between attempts...
time.sleep(0.5)
return {}
def destroy(name, call=None):
"""Destroy a node. Will check termination protection and warn if enabled.
CLI Example:
.. code-block:: bash
salt-cloud --destroy mymachine
"""
if call == "function":
raise SaltCloudSystemExit(
"The destroy action must be called with -d, --destroy, -a or --action."
)
__utils__["cloud.fire_event"](
"event",
"destroying instance",
f"salt/cloud/{name}/destroying",
args={"name": name},
sock_dir=__opts__["sock_dir"],
transport=__opts__["transport"],
)
data = show_instance(name, call="action")
node = query(
method="servers",
server_id=data["id"],
command="action",
args={"action": "terminate"},
http_method="POST",
)
__utils__["cloud.fire_event"](
"event",
"destroyed instance",
f"salt/cloud/{name}/destroyed",
args={"name": name},
sock_dir=__opts__["sock_dir"],
transport=__opts__["transport"],
)
if __opts__.get("update_cachedir", False) is True:
__utils__["cloud.delete_minion_cachedir"](
name, _get_active_provider_name().split(":")[0], __opts__
)
return node
Zerion Mini Shell 1.0