Mini Shell
"""
The generic libcloud template used to create the connections and deploy the
cloud virtual machines
"""
import logging
import os
import salt.client
import salt.config
import salt.utils.cloud
import salt.utils.data
import salt.utils.event
from salt.exceptions import SaltCloudNotFound, SaltCloudSystemExit
# pylint: disable=W0611
try:
import libcloud
from libcloud.compute.deployment import MultiStepDeployment, ScriptDeployment
from libcloud.compute.providers import get_driver
from libcloud.compute.types import NodeState, Provider
HAS_LIBCLOUD = True
LIBCLOUD_VERSION_INFO = tuple(
int(part)
for part in libcloud.__version__.replace("-", ".")
.replace("rc", ".")
.split(".")[:3]
)
except ImportError:
HAS_LIBCLOUD = False
LIBCLOUD_VERSION_INFO = (1000,)
# pylint: enable=W0611
log = logging.getLogger(__name__)
LIBCLOUD_MINIMAL_VERSION = (1, 5, 0)
def check_libcloud_version(reqver=LIBCLOUD_MINIMAL_VERSION, why=None):
"""
Compare different libcloud versions
"""
if not HAS_LIBCLOUD:
return False
if not isinstance(reqver, (list, tuple)):
raise RuntimeError(
"'reqver' needs to passed as a tuple or list, i.e., (1, 5, 0)"
)
if LIBCLOUD_VERSION_INFO >= reqver:
return libcloud.__version__
errormsg = (
"Your version of libcloud is {}. salt-cloud requires >= libcloud {}".format(
libcloud.__version__, ".".join([str(num) for num in reqver])
)
)
if why:
errormsg += f" for {why}"
errormsg += ". Please upgrade."
raise ImportError(errormsg)
def get_node(conn, name):
"""
Return a libcloud node for the named VM
"""
nodes = conn.list_nodes()
for node in nodes:
if node.name == name:
__utils__["cloud.cache_node"](
salt.utils.data.simple_types_filter(node.__dict__),
__active_provider_name__,
__opts__,
)
return node
def avail_locations(conn=None, call=None):
"""
Return a dict of all available VM locations on the cloud provider with
relevant data
"""
if call == "action":
raise SaltCloudSystemExit(
"The avail_locations function must be called with "
"-f or --function, or with the --list-locations option"
)
if not conn:
conn = get_conn() # pylint: disable=E0602
locations = conn.list_locations()
ret = {}
for img in locations:
img_name = str(img.name)
ret[img_name] = {}
for attr in dir(img):
if attr.startswith("_") or attr == "driver":
continue
attr_value = getattr(img, attr)
ret[img_name][attr] = attr_value
return ret
def avail_images(conn=None, call=None):
"""
Return a dict of all available VM images on the cloud provider with
relevant data
"""
if call == "action":
raise SaltCloudSystemExit(
"The avail_images function must be called with "
"-f or --function, or with the --list-images option"
)
if not conn:
conn = get_conn() # pylint: disable=E0602
images = conn.list_images()
ret = {}
for img in images:
img_name = str(img.name)
ret[img_name] = {}
for attr in dir(img):
if attr.startswith("_") or attr in ("driver", "get_uuid"):
continue
attr_value = getattr(img, attr)
ret[img_name][attr] = attr_value
return ret
def avail_sizes(conn=None, call=None):
"""
Return a dict of all available VM images on the cloud provider with
relevant data
"""
if call == "action":
raise SaltCloudSystemExit(
"The avail_sizes function must be called with "
"-f or --function, or with the --list-sizes option"
)
if not conn:
conn = get_conn() # pylint: disable=E0602
sizes = conn.list_sizes()
ret = {}
for size in sizes:
size_name = str(size.name)
ret[size_name] = {}
for attr in dir(size):
if attr.startswith("_") or attr in ("driver", "get_uuid"):
continue
try:
attr_value = getattr(size, attr)
except Exception: # pylint: disable=broad-except
pass
ret[size_name][attr] = attr_value
return ret
def get_location(conn, vm_):
"""
Return the location object to use
"""
locations = conn.list_locations()
vm_location = salt.config.get_cloud_config_value("location", vm_, __opts__)
for img in locations:
img_id = str(img.id)
img_name = str(img.name)
if vm_location and vm_location in (img_id, img_name):
return img
raise SaltCloudNotFound(
f"The specified location, '{vm_location}', could not be found."
)
def get_image(conn, vm_):
"""
Return the image object to use
"""
images = conn.list_images()
vm_image = salt.config.get_cloud_config_value("image", vm_, __opts__)
for img in images:
img_id = str(img.id)
img_name = str(img.name)
if vm_image and vm_image in (img_id, img_name):
return img
raise SaltCloudNotFound(f"The specified image, '{vm_image}', could not be found.")
def get_size(conn, vm_):
"""
Return the VM's size object
"""
sizes = conn.list_sizes()
vm_size = salt.config.get_cloud_config_value("size", vm_, __opts__)
if not vm_size:
return sizes[0]
for size in sizes:
if vm_size and str(vm_size) in (
str(size.id),
str(size.name),
):
return size
raise SaltCloudNotFound(f"The specified size, '{vm_size}', could not be found.")
def script(vm_):
"""
Return the script deployment object
"""
return ScriptDeployment(
salt.utils.cloud.os_script(
salt.config.get_cloud_config_value("os", vm_, __opts__),
vm_,
__opts__,
salt.utils.cloud.salt_config_to_yaml(
salt.utils.cloud.minion_config(__opts__, vm_)
),
)
)
def destroy(name, conn=None, call=None):
"""
Delete a single VM
"""
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"],
)
if not conn:
conn = get_conn() # pylint: disable=E0602
node = get_node(conn, name)
profiles = get_configured_provider()["profiles"] # pylint: disable=E0602
if node is None:
log.error("Unable to find the VM %s", name)
profile = None
if "metadata" in node.extra and "profile" in node.extra["metadata"]:
profile = node.extra["metadata"]["profile"]
flush_mine_on_destroy = False
if profile and profile in profiles and "flush_mine_on_destroy" in profiles[profile]:
flush_mine_on_destroy = profiles[profile]["flush_mine_on_destroy"]
if flush_mine_on_destroy:
log.info("Clearing Salt Mine: %s", name)
mopts_ = salt.config.DEFAULT_MINION_OPTS
conf_path = "/".join(__opts__["conf_file"].split("/")[:-1])
mopts_.update(salt.config.minion_config(os.path.join(conf_path, "minion")))
with salt.client.get_local_client(mopts=mopts_) as client:
minions = client.cmd(name, "mine.flush")
log.info("Clearing Salt Mine: %s, %s", name, flush_mine_on_destroy)
log.info("Destroying VM: %s", name)
ret = conn.destroy_node(node)
if ret:
log.info("Destroyed VM: %s", name)
# Fire destroy action
__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__["delete_sshkeys"] is True:
public_ips = getattr(node, __opts__.get("ssh_interface", "public_ips"))
if public_ips:
salt.utils.cloud.remove_sshkey(public_ips[0])
private_ips = getattr(node, __opts__.get("ssh_interface", "private_ips"))
if private_ips:
salt.utils.cloud.remove_sshkey(private_ips[0])
if __opts__.get("update_cachedir", False) is True:
__utils__["cloud.delete_minion_cachedir"](
name, __active_provider_name__.split(":")[0], __opts__
)
return True
log.error("Failed to Destroy VM: %s", name)
return False
def reboot(name, conn=None):
"""
Reboot a single VM
"""
if not conn:
conn = get_conn() # pylint: disable=E0602
node = get_node(conn, name)
if node is None:
log.error("Unable to find the VM %s", name)
log.info("Rebooting VM: %s", name)
ret = conn.reboot_node(node)
if ret:
log.info("Rebooted VM: %s", name)
# Fire reboot action
__utils__["cloud.fire_event"](
"event",
f"{name} has been rebooted",
f"salt/cloud/{name}/rebooting",
args={"name": name},
sock_dir=__opts__["sock_dir"],
transport=__opts__["transport"],
)
return True
log.error("Failed to reboot VM: %s", name)
return False
def list_nodes(conn=None, call=None):
"""
Return a list of the VMs that are on the provider
"""
if call == "action":
raise SaltCloudSystemExit(
"The list_nodes function must be called with -f or --function."
)
if not conn:
conn = get_conn() # pylint: disable=E0602
nodes = conn.list_nodes()
ret = {}
for node in nodes:
ret[node.name] = {
"id": node.id,
"image": node.image,
"name": node.name,
"private_ips": node.private_ips,
"public_ips": node.public_ips,
"size": node.size,
"state": str(node.state).upper(),
}
return ret
def list_nodes_full(conn=None, call=None):
"""
Return a list of the VMs that are on the provider, with all fields
"""
if call == "action":
raise SaltCloudSystemExit(
"The list_nodes_full function must be called with -f or --function."
)
if not conn:
conn = get_conn() # pylint: disable=E0602
nodes = conn.list_nodes()
ret = {}
for node in nodes:
pairs = {}
for key, value in zip(node.__dict__, node.__dict__.values()):
pairs[key] = value
ret[node.name] = pairs
del ret[node.name]["driver"]
__utils__["cloud.cache_node_list"](
ret, __active_provider_name__.split(":")[0], __opts__
)
return ret
def list_nodes_select(conn=None, call=None):
"""
Return a list of the VMs that are on the provider, with select fields
"""
if not conn:
conn = get_conn() # pylint: disable=E0602
return salt.utils.cloud.list_nodes_select(
list_nodes_full(conn, "function"),
__opts__["query.selection"],
call,
)
def show_instance(name, call=None):
"""
Show the details from the provider concerning an instance
"""
if call != "action":
raise SaltCloudSystemExit(
"The show_instance action must be called with -a or --action."
)
nodes = list_nodes_full()
__utils__["cloud.cache_node"](nodes[name], __active_provider_name__, __opts__)
return nodes[name]
def conn_has_method(conn, method_name):
"""
Find if the provided connection object has a specific method
"""
if method_name in dir(conn):
return True
log.error("Method '%s' not yet supported!", method_name)
return False
Zerion Mini Shell 1.0