mirror of
https://github.com/saltstack/salt.git
synced 2025-04-10 23:01:39 +00:00
361 lines
9.6 KiB
Python
361 lines
9.6 KiB
Python
"""
|
|
Vagrant Cloud Driver
|
|
====================
|
|
|
|
The Vagrant cloud is designed to "vagrant up" a virtual machine as a
|
|
Salt minion.
|
|
|
|
Use of this module requires some configuration in cloud profile and provider
|
|
files as described in the
|
|
:ref:`Getting Started with Vagrant <getting-started-with-vagrant>` documentation.
|
|
|
|
.. versionadded:: 2018.3.0
|
|
|
|
|
|
"""
|
|
|
|
import logging
|
|
import os
|
|
import tempfile
|
|
|
|
import salt.client
|
|
import salt.config as config
|
|
import salt.utils.cloud
|
|
from salt._compat import ipaddress
|
|
from salt.exceptions import SaltCloudException, SaltCloudSystemExit, SaltInvocationError
|
|
|
|
log = logging.getLogger(__name__)
|
|
|
|
|
|
def __virtual__():
|
|
"""
|
|
Needs no special configuration
|
|
"""
|
|
return True
|
|
|
|
|
|
def _get_active_provider_name():
|
|
try:
|
|
return __active_provider_name__.value()
|
|
except AttributeError:
|
|
return __active_provider_name__
|
|
|
|
|
|
def avail_locations(call=None):
|
|
r"""
|
|
This function returns a list of locations available.
|
|
|
|
CLI Example:
|
|
|
|
.. code-block:: bash
|
|
|
|
salt-cloud --list-locations my-cloud-provider
|
|
|
|
# \[ vagrant will always returns an empty dictionary \]
|
|
|
|
"""
|
|
|
|
return {}
|
|
|
|
|
|
def avail_images(call=None):
|
|
"""This function returns a list of images available for this cloud provider.
|
|
vagrant will return a list of profiles.
|
|
salt-cloud --list-images my-cloud-provider
|
|
"""
|
|
vm_ = get_configured_provider()
|
|
return {"Profiles": [profile for profile in vm_["profiles"]]}
|
|
|
|
|
|
def avail_sizes(call=None):
|
|
r"""
|
|
This function returns a list of sizes available for this cloud provider.
|
|
|
|
CLI Example:
|
|
|
|
.. code-block:: bash
|
|
|
|
salt-cloud --list-sizes my-cloud-provider
|
|
|
|
# \[ vagrant always returns an empty dictionary \]
|
|
|
|
"""
|
|
return {}
|
|
|
|
|
|
def list_nodes(call=None):
|
|
"""
|
|
List the nodes which have salt-cloud:driver:vagrant grains.
|
|
|
|
CLI Example:
|
|
|
|
.. code-block:: bash
|
|
|
|
salt-cloud -Q
|
|
"""
|
|
nodes = _list_nodes(call)
|
|
return _build_required_items(nodes)
|
|
|
|
|
|
def _build_required_items(nodes):
|
|
ret = {}
|
|
for name, grains in nodes.items():
|
|
if grains:
|
|
private_ips = []
|
|
public_ips = []
|
|
ips = grains["ipv4"] + grains["ipv6"]
|
|
for adrs in ips:
|
|
ip_ = ipaddress.ip_address(adrs)
|
|
if not ip_.is_loopback:
|
|
if ip_.is_private:
|
|
private_ips.append(adrs)
|
|
else:
|
|
public_ips.append(adrs)
|
|
|
|
ret[name] = {
|
|
"id": grains["id"],
|
|
"image": grains["salt-cloud"]["profile"],
|
|
"private_ips": private_ips,
|
|
"public_ips": public_ips,
|
|
"size": "",
|
|
"state": "running",
|
|
}
|
|
|
|
return ret
|
|
|
|
|
|
def list_nodes_full(call=None):
|
|
"""
|
|
List the nodes, ask all 'vagrant' minions, return dict of grains (enhanced).
|
|
|
|
CLI Example:
|
|
|
|
.. code-block:: bash
|
|
|
|
salt-call -F
|
|
"""
|
|
ret = _list_nodes(call)
|
|
|
|
for (
|
|
key,
|
|
grains,
|
|
) in ret.items(): # clean up some hyperverbose grains -- everything is too much
|
|
try:
|
|
del (
|
|
grains["cpu_flags"],
|
|
grains["disks"],
|
|
grains["pythonpath"],
|
|
grains["dns"],
|
|
grains["gpus"],
|
|
)
|
|
except KeyError:
|
|
pass # ignore absence of things we are eliminating
|
|
except TypeError:
|
|
del ret[key] # eliminate all reference to unexpected (None) values.
|
|
|
|
reqs = _build_required_items(ret)
|
|
for name in ret:
|
|
ret[name].update(reqs[name])
|
|
return ret
|
|
|
|
|
|
def _list_nodes(call=None):
|
|
"""
|
|
List the nodes, ask all 'vagrant' minions, return dict of grains.
|
|
"""
|
|
with salt.client.LocalClient() as local:
|
|
return local.cmd(
|
|
"salt-cloud:driver:vagrant", "grains.items", "", tgt_type="grain"
|
|
)
|
|
|
|
|
|
def list_nodes_select(call=None):
|
|
"""
|
|
Return a list of the minions that have salt-cloud grains, with
|
|
select fields.
|
|
"""
|
|
return salt.utils.cloud.list_nodes_select(
|
|
list_nodes_full("function"),
|
|
__opts__["query.selection"],
|
|
call,
|
|
)
|
|
|
|
|
|
def show_instance(name, call=None):
|
|
"""
|
|
List the a single node, return dict of grains.
|
|
"""
|
|
with salt.client.LocalClient() as local:
|
|
ret = local.cmd(name, "grains.items", "")
|
|
reqs = _build_required_items(ret)
|
|
ret[name].update(reqs[name])
|
|
return ret
|
|
|
|
|
|
def _get_my_info(name):
|
|
with salt.client.LocalClient() as local:
|
|
return local.cmd(name, "grains.get", ["salt-cloud"])
|
|
|
|
|
|
def create(vm_):
|
|
"""
|
|
Provision a single machine
|
|
|
|
CLI Example:
|
|
|
|
.. code-block:: bash
|
|
|
|
salt-cloud -p my_profile new_node_1
|
|
|
|
"""
|
|
name = vm_["name"]
|
|
machine = config.get_cloud_config_value("machine", vm_, __opts__, default="")
|
|
vm_["machine"] = machine
|
|
host = config.get_cloud_config_value("host", vm_, __opts__, default=NotImplemented)
|
|
vm_["cwd"] = config.get_cloud_config_value("cwd", vm_, __opts__, default="/")
|
|
vm_["runas"] = config.get_cloud_config_value(
|
|
"vagrant_runas", vm_, __opts__, default=os.getenv("SUDO_USER")
|
|
)
|
|
vm_["timeout"] = config.get_cloud_config_value(
|
|
"vagrant_up_timeout", vm_, __opts__, default=300
|
|
)
|
|
vm_["vagrant_provider"] = config.get_cloud_config_value(
|
|
"vagrant_provider", vm_, __opts__, default=""
|
|
)
|
|
vm_["grains"] = {"salt-cloud:vagrant": {"host": host, "machine": machine}}
|
|
|
|
log.info("sending 'vagrant.init %s machine=%s' command to %s", name, machine, host)
|
|
|
|
with salt.client.LocalClient() as local:
|
|
ret = local.cmd(host, "vagrant.init", [name], kwarg={"vm": vm_, "start": True})
|
|
log.info("response ==> %s", ret[host])
|
|
|
|
network_mask = config.get_cloud_config_value(
|
|
"network_mask", vm_, __opts__, default=""
|
|
)
|
|
if "ssh_host" not in vm_:
|
|
ret = local.cmd(
|
|
host,
|
|
"vagrant.get_ssh_config",
|
|
[name],
|
|
kwarg={"network_mask": network_mask, "get_private_key": True},
|
|
)[host]
|
|
with tempfile.NamedTemporaryFile() as pks:
|
|
if "private_key" not in vm_ and ret and ret.get("private_key", False):
|
|
pks.write(ret["private_key"])
|
|
pks.flush()
|
|
log.debug("wrote private key to %s", pks.name)
|
|
vm_["key_filename"] = pks.name
|
|
if "ssh_host" not in vm_:
|
|
try:
|
|
vm_.setdefault("ssh_username", ret["ssh_username"])
|
|
if ret.get("ip_address"):
|
|
vm_["ssh_host"] = ret["ip_address"]
|
|
else: # if probe failed or not used, use Vagrant's reported ssh info
|
|
vm_["ssh_host"] = ret["ssh_host"]
|
|
vm_.setdefault("ssh_port", ret["ssh_port"])
|
|
except (KeyError, TypeError):
|
|
raise SaltInvocationError(
|
|
f"Insufficient SSH addressing information for {name}"
|
|
)
|
|
|
|
log.info(
|
|
"Provisioning machine %s as node %s using ssh %s",
|
|
machine,
|
|
name,
|
|
vm_["ssh_host"],
|
|
)
|
|
ret = __utils__["cloud.bootstrap"](vm_, __opts__)
|
|
return ret
|
|
|
|
|
|
def get_configured_provider():
|
|
"""
|
|
Return the first configured instance.
|
|
"""
|
|
ret = config.is_provider_configured(
|
|
__opts__, _get_active_provider_name() or "vagrant", ""
|
|
)
|
|
return ret
|
|
|
|
|
|
# noinspection PyTypeChecker
|
|
def destroy(name, call=None):
|
|
"""
|
|
Destroy a node.
|
|
|
|
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."
|
|
)
|
|
|
|
opts = __opts__
|
|
|
|
__utils__["cloud.fire_event"](
|
|
"event",
|
|
"destroying instance",
|
|
f"salt/cloud/{name}/destroying",
|
|
args={"name": name},
|
|
sock_dir=opts["sock_dir"],
|
|
transport=opts["transport"],
|
|
)
|
|
my_info = _get_my_info(name)
|
|
if my_info:
|
|
profile_name = my_info[name]["profile"]
|
|
profile = opts["profiles"][profile_name]
|
|
host = profile["host"]
|
|
with salt.client.LocalClient() as local:
|
|
ret = local.cmd(host, "vagrant.destroy", [name])
|
|
|
|
if ret[host]:
|
|
__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 {"Destroyed": f"{name} was destroyed."}
|
|
else:
|
|
return {"Error": f"Error destroying {name}"}
|
|
else:
|
|
return {"Error": f"No response from {name}. Cannot destroy."}
|
|
|
|
|
|
# noinspection PyTypeChecker
|
|
def reboot(name, call=None):
|
|
"""
|
|
Reboot a vagrant minion.
|
|
|
|
name
|
|
The name of the VM to reboot.
|
|
|
|
CLI Example:
|
|
|
|
.. code-block:: bash
|
|
|
|
salt-cloud -a reboot vm_name
|
|
"""
|
|
if call != "action":
|
|
raise SaltCloudException(
|
|
"The reboot action must be called with -a or --action."
|
|
)
|
|
my_info = _get_my_info(name)
|
|
profile_name = my_info[name]["profile"]
|
|
profile = __opts__["profiles"][profile_name]
|
|
host = profile["host"]
|
|
with salt.client.LocalClient() as local:
|
|
return local.cmd(host, "vagrant.reboot", [name])
|