Mini Shell
"""
Packet Cloud Module Using Packet's Python API Client
====================================================
The Packet cloud module is used to control access to the Packet VPS system.
Use of this module only requires the ``token`` parameter.
Set up the cloud configuration at ``/etc/salt/cloud.providers`` or ``/etc/salt/cloud.providers.d/packet.conf``:
The Packet profile requires ``size``, ``image``, ``location``, ``project_id``
Optional profile parameters:
- ``storage_size`` - min value is 10, defines Gigabytes of storage that will be attached to device.
- ``storage_tier`` - storage_1 - Standard Plan, storage_2 - Performance Plan
- ``snapshot_count`` - int
- ``snapshot_frequency`` - string - possible values:
- 1min
- 15min
- 1hour
- 1day
- 1week
- 1month
- 1year
This driver requires Packet's client library: https://pypi.python.org/pypi/packet-python
.. code-block:: yaml
packet-provider:
minion:
master: 192.168.50.10
driver: packet
token: ewr23rdf35wC8oNjJrhmHa87rjSXzJyi
private_key: /root/.ssh/id_rsa
packet-profile:
provider: packet-provider
size: baremetal_0
image: ubuntu_16_04_image
location: ewr1
project_id: a64d000b-d47c-4d26-9870-46aac43010a6
storage_size: 10
storage_tier: storage_1
storage_snapshot_count: 1
storage_snapshot_frequency: 15min
"""
import logging
import pprint
import time
import salt.config as config
import salt.utils.cloud
from salt.cloud.libcloudfuncs import get_image, get_size, script, show_instance
from salt.exceptions import SaltCloudException, SaltCloudSystemExit
from salt.utils.functools import namespaced_function
try:
import packet
HAS_PACKET = True
except ImportError:
HAS_PACKET = False
get_size = namespaced_function(get_size, globals())
get_image = namespaced_function(get_image, globals())
script = namespaced_function(script, globals())
show_instance = namespaced_function(show_instance, globals())
# Get logging started
log = logging.getLogger(__name__)
__virtualname__ = "packet"
# Only load this module if the Packet configuration is in place.
def __virtual__():
"""
Check for Packet configs.
"""
if HAS_PACKET is False:
return False, "The packet python library is not installed"
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 available Packet os images.
CLI Example:
.. code-block:: bash
salt-cloud --list-images packet-provider
salt-cloud -f avail_images packet-provider
"""
if call == "action":
raise SaltCloudException(
"The avail_images function must be called with -f or --function."
)
ret = {}
vm_ = get_configured_provider()
manager = packet.Manager(auth_token=vm_["token"])
ret = {}
for os_system in manager.list_operating_systems():
ret[os_system.name] = os_system.__dict__
return ret
def avail_locations(call=None):
"""
Return available Packet datacenter locations.
CLI Example:
.. code-block:: bash
salt-cloud --list-locations packet-provider
salt-cloud -f avail_locations packet-provider
"""
if call == "action":
raise SaltCloudException(
"The avail_locations function must be called with -f or --function."
)
vm_ = get_configured_provider()
manager = packet.Manager(auth_token=vm_["token"])
ret = {}
for facility in manager.list_facilities():
ret[facility.name] = facility.__dict__
return ret
def avail_sizes(call=None):
"""
Return available Packet sizes.
CLI Example:
.. code-block:: bash
salt-cloud --list-sizes packet-provider
salt-cloud -f avail_sizes packet-provider
"""
if call == "action":
raise SaltCloudException(
"The avail_locations function must be called with -f or --function."
)
vm_ = get_configured_provider()
manager = packet.Manager(auth_token=vm_["token"])
ret = {}
for plan in manager.list_plans():
ret[plan.name] = plan.__dict__
return ret
def avail_projects(call=None):
"""
Return available Packet projects.
CLI Example:
.. code-block:: bash
salt-cloud -f avail_projects packet-provider
"""
if call == "action":
raise SaltCloudException(
"The avail_projects function must be called with -f or --function."
)
vm_ = get_configured_provider()
manager = packet.Manager(auth_token=vm_["token"])
ret = {}
for project in manager.list_projects():
ret[project.name] = project.__dict__
return ret
def _wait_for_status(status_type, object_id, status=None, timeout=500, quiet=True):
"""
Wait for a certain status from Packet.
status_type
device or volume
object_id
The ID of the Packet device or volume to wait on. Required.
status
The status to wait for.
timeout
The amount of time to wait for a status to update.
quiet
Log status updates to debug logs when False. Otherwise, logs to info.
"""
if status is None:
status = "ok"
interval = 5
iterations = int(timeout / interval)
vm_ = get_configured_provider()
manager = packet.Manager(auth_token=vm_["token"])
for i in range(0, iterations):
get_object = getattr(manager, f"get_{status_type}")
obj = get_object(object_id)
if obj.state == status:
return obj
time.sleep(interval)
log.log(
logging.INFO if not quiet else logging.DEBUG,
"Status for Packet %s is '%s', waiting for '%s'.",
object_id,
obj.state,
status,
)
return obj
def is_profile_configured(vm_):
try:
# Check for required profile parameters before sending any API calls.
if (
vm_["profile"]
and config.is_profile_configured(
__opts__,
_get_active_provider_name() or __virtualname__,
vm_["profile"],
vm_=vm_,
)
is False
):
return False
alias, driver = _get_active_provider_name().split(":")
profile_data = __opts__["providers"][alias][driver]["profiles"][vm_["profile"]]
if profile_data.get("storage_size") or profile_data.get("storage_tier"):
required_keys = ["storage_size", "storage_tier"]
for key in required_keys:
if profile_data.get(key) is None:
log.error(
"both storage_size and storage_tier required for "
"profile %s. Please check your profile configuration",
vm_["profile"],
)
return False
locations = avail_locations()
for location in locations.values():
if location["code"] == profile_data["location"]:
if "storage" not in location["features"]:
log.error(
"Chosen location %s for profile %s does not "
"support storage feature. Please check your "
"profile configuration",
location["code"],
vm_["profile"],
)
return False
if profile_data.get("storage_snapshot_count") or profile_data.get(
"storage_snapshot_frequency"
):
required_keys = ["storage_size", "storage_tier"]
for key in required_keys:
if profile_data.get(key) is None:
log.error(
"both storage_snapshot_count and "
"storage_snapshot_frequency required for profile "
"%s. Please check your profile configuration",
vm_["profile"],
)
return False
except AttributeError:
pass
return True
def create(vm_):
"""
Create a single Packet VM.
"""
name = vm_["name"]
if not is_profile_configured(vm_):
return False
__utils__["cloud.fire_event"](
"event",
"starting create",
f"salt/cloud/{name}/creating",
args=__utils__["cloud.filter_event"](
"creating", vm_, ["name", "profile", "provider", "driver"]
),
sock_dir=__opts__["sock_dir"],
transport=__opts__["transport"],
)
log.info("Creating Packet VM %s", name)
manager = packet.Manager(auth_token=vm_["token"])
__utils__["cloud.fire_event"](
"event",
"requesting instance",
"salt/cloud/{}/requesting".format(vm_["name"]),
args=__utils__["cloud.filter_event"](
"requesting", vm_, ["name", "profile", "provider", "driver"]
),
sock_dir=__opts__["sock_dir"],
transport=__opts__["transport"],
)
device = manager.create_device(
project_id=vm_["project_id"],
hostname=name,
plan=vm_["size"],
facility=vm_["location"],
operating_system=vm_["image"],
)
device = _wait_for_status("device", device.id, status="active")
if device.state != "active":
log.error(
"Error creating %s on PACKET\n\nwhile waiting for initial ready status",
name,
exc_info_on_loglevel=logging.DEBUG,
)
# Define which ssh_interface to use
ssh_interface = _get_ssh_interface(vm_)
# Pass the correct IP address to the bootstrap ssh_host key
if ssh_interface == "private_ips":
for ip in device.ip_addresses:
if ip["public"] is False:
vm_["ssh_host"] = ip["address"]
break
else:
for ip in device.ip_addresses:
if ip["public"] is True:
vm_["ssh_host"] = ip["address"]
break
key_filename = config.get_cloud_config_value(
"private_key", vm_, __opts__, search_global=False, default=None
)
vm_["key_filename"] = key_filename
vm_["private_key"] = key_filename
# Bootstrap!
ret = __utils__["cloud.bootstrap"](vm_, __opts__)
ret.update({"device": device.__dict__})
if vm_.get("storage_tier") and vm_.get("storage_size"):
# create storage and attach it to device
volume = manager.create_volume(
vm_["project_id"],
f"{name}_storage",
vm_.get("storage_tier"),
vm_.get("storage_size"),
vm_.get("location"),
snapshot_count=vm_.get("storage_snapshot_count", 0),
snapshot_frequency=vm_.get("storage_snapshot_frequency"),
)
volume.attach(device.id)
volume = _wait_for_status("volume", volume.id, status="active")
if volume.state != "active":
log.error(
"Error creating %s on PACKET\n\nwhile waiting for initial ready status",
name,
exc_info_on_loglevel=logging.DEBUG,
)
ret.update({"volume": volume.__dict__})
log.info("Created Cloud VM '%s'", name)
log.debug("'%s' VM creation details:\n%s", name, pprint.pformat(device.__dict__))
__utils__["cloud.fire_event"](
"event",
"created instance",
f"salt/cloud/{name}/created",
args=__utils__["cloud.filter_event"](
"created", vm_, ["name", "profile", "provider", "driver"]
),
sock_dir=__opts__["sock_dir"],
transport=__opts__["transport"],
)
return ret
def list_nodes_full(call=None):
"""
List devices, with all available information.
CLI Example:
.. code-block:: bash
salt-cloud -F
salt-cloud --full-query
salt-cloud -f list_nodes_full packet-provider
..
"""
if call == "action":
raise SaltCloudException(
"The list_nodes_full function must be called with -f or --function."
)
ret = {}
for device in get_devices_by_token():
ret[device.hostname] = device.__dict__
return ret
def list_nodes_min(call=None):
"""
Return a list of the VMs that are on the provider. Only a list of VM names and
their state is returned. This is the minimum amount of information needed to
check for existing VMs.
.. versionadded:: 2015.8.0
CLI Example:
.. code-block:: bash
salt-cloud -f list_nodes_min packet-provider
salt-cloud --function list_nodes_min packet-provider
"""
if call == "action":
raise SaltCloudSystemExit(
"The list_nodes_min function must be called with -f or --function."
)
ret = {}
for device in get_devices_by_token():
ret[device.hostname] = {"id": device.id, "state": device.state}
return ret
def list_nodes_select(call=None):
"""
Return a list of the VMs that are on the provider, with select fields.
"""
return salt.utils.cloud.list_nodes_select(
list_nodes_full(),
__opts__["query.selection"],
call,
)
def get_devices_by_token():
vm_ = get_configured_provider()
manager = packet.Manager(auth_token=vm_["token"])
devices = []
for profile_name in vm_["profiles"]:
profile = vm_["profiles"][profile_name]
devices.extend(manager.list_devices(profile["project_id"]))
return devices
def list_nodes(call=None):
"""
Returns a list of devices, keeping only a brief listing.
CLI Example:
.. code-block:: bash
salt-cloud -Q
salt-cloud --query
salt-cloud -f list_nodes packet-provider
..
"""
if call == "action":
raise SaltCloudException(
"The list_nodes function must be called with -f or --function."
)
ret = {}
for device in get_devices_by_token():
ret[device.hostname] = device.__dict__
return ret
def destroy(name, call=None):
"""
Destroys a Packet device by name.
name
The hostname of VM to be be destroyed.
CLI Example:
.. code-block:: bash
salt-cloud -d name
"""
if call == "function":
raise SaltCloudException(
"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"],
)
vm_ = get_configured_provider()
manager = packet.Manager(auth_token=vm_["token"])
nodes = list_nodes_min()
node = nodes[name]
for project in manager.list_projects():
for volume in manager.list_volumes(project.id):
if volume.attached_to == node["id"]:
volume.detach()
volume.delete()
break
manager.call_api("devices/{id}".format(id=node["id"]), type="DELETE")
__utils__["cloud.fire_event"](
"event",
"destroyed instance",
f"salt/cloud/{name}/destroyed",
args={"name": name},
sock_dir=__opts__["sock_dir"],
transport=__opts__["transport"],
)
return {}
def _get_ssh_interface(vm_):
"""
Return the ssh_interface type to connect to. Either 'public_ips' (default)
or 'private_ips'.
"""
return config.get_cloud_config_value(
"ssh_interface", vm_, __opts__, default="public_ips", search_global=False
)
Zerion Mini Shell 1.0