Mini Shell
"""
SoftLayer Cloud Module
======================
The SoftLayer cloud module is used to control access to the SoftLayer VPS
system.
Use of this module only requires the ``apikey`` parameter. Set up the cloud
configuration at:
``/etc/salt/cloud.providers`` or ``/etc/salt/cloud.providers.d/softlayer.conf``:
.. code-block:: yaml
my-softlayer-config:
# SoftLayer account api key
user: MYLOGIN
apikey: JVkbSJDGHSDKUKSDJfhsdklfjgsjdkflhjlsdfffhgdgjkenrtuinv
driver: softlayer
The SoftLayer Python Library needs to be installed in order to use the
SoftLayer salt.cloud modules. See: https://pypi.python.org/pypi/SoftLayer
:depends: softlayer
"""
import logging
import time
import salt.config as config
import salt.utils.cloud
from salt.exceptions import SaltCloudSystemExit
# Attempt to import softlayer lib
try:
import SoftLayer
HAS_SLLIBS = True
except ImportError:
HAS_SLLIBS = False
# Get logging started
log = logging.getLogger(__name__)
__virtualname__ = "softlayer"
# Only load in this module if the SoftLayer configurations are in place
def __virtual__():
"""
Check for SoftLayer configurations.
"""
if get_configured_provider() is False:
return False
if get_dependencies() 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__, ("apikey",)
)
def get_dependencies():
"""
Warn if dependencies aren't met.
"""
return config.check_driver_dependencies(__virtualname__, {"softlayer": HAS_SLLIBS})
def script(vm_):
"""
Return the script deployment object
"""
deploy_script = salt.utils.cloud.os_script(
config.get_cloud_config_value("script", vm_, __opts__),
vm_,
__opts__,
salt.utils.cloud.salt_config_to_yaml(
salt.utils.cloud.minion_config(__opts__, vm_)
),
)
return deploy_script
def get_conn(service="SoftLayer_Virtual_Guest"):
"""
Return a conn object for the passed VM data
"""
client = SoftLayer.Client(
username=config.get_cloud_config_value(
"user", get_configured_provider(), __opts__, search_global=False
),
api_key=config.get_cloud_config_value(
"apikey", get_configured_provider(), __opts__, search_global=False
),
)
return client[service]
def avail_locations(call=None):
"""
List all available locations
"""
if call == "action":
raise SaltCloudSystemExit(
"The avail_locations function must be called with "
"-f or --function, or with the --list-locations option"
)
ret = {}
conn = get_conn()
response = conn.getCreateObjectOptions()
# return response
for datacenter in response["datacenters"]:
# return data center
ret[datacenter["template"]["datacenter"]["name"]] = {
"name": datacenter["template"]["datacenter"]["name"],
}
return ret
def avail_sizes(call=None):
"""
Return a dict of all available VM sizes on the cloud provider with
relevant data. This data is provided in three dicts.
"""
if call == "action":
raise SaltCloudSystemExit(
"The avail_sizes function must be called with "
"-f or --function, or with the --list-sizes option"
)
ret = {
"block devices": {},
"memory": {},
"processors": {},
}
conn = get_conn()
response = conn.getCreateObjectOptions()
for device in response["blockDevices"]:
# return device['template']['blockDevices']
ret["block devices"][device["itemPrice"]["item"]["description"]] = {
"name": device["itemPrice"]["item"]["description"],
"capacity": device["template"]["blockDevices"][0]["diskImage"]["capacity"],
}
for memory in response["memory"]:
ret["memory"][memory["itemPrice"]["item"]["description"]] = {
"name": memory["itemPrice"]["item"]["description"],
"maxMemory": memory["template"]["maxMemory"],
}
for processors in response["processors"]:
ret["processors"][processors["itemPrice"]["item"]["description"]] = {
"name": processors["itemPrice"]["item"]["description"],
"start cpus": processors["template"]["startCpus"],
}
return ret
def avail_images(call=None):
"""
Return a dict of all available VM images on the cloud provider.
"""
if call == "action":
raise SaltCloudSystemExit(
"The avail_images function must be called with "
"-f or --function, or with the --list-images option"
)
ret = {}
conn = get_conn()
response = conn.getCreateObjectOptions()
for image in response["operatingSystems"]:
ret[image["itemPrice"]["item"]["description"]] = {
"name": image["itemPrice"]["item"]["description"],
"template": image["template"]["operatingSystemReferenceCode"],
}
return ret
def list_custom_images(call=None):
"""
Return a dict of all custom VM images on the cloud provider.
"""
if call != "function":
raise SaltCloudSystemExit(
"The list_vlans function must be called with -f or --function."
)
ret = {}
conn = get_conn("SoftLayer_Account")
response = conn.getBlockDeviceTemplateGroups()
for image in response:
if "globalIdentifier" not in image:
continue
ret[image["name"]] = {
"id": image["id"],
"name": image["name"],
"globalIdentifier": image["globalIdentifier"],
}
if "note" in image:
ret[image["name"]]["note"] = image["note"]
return ret
def get_location(vm_=None):
"""
Return the location to use, in this order:
- CLI parameter
- VM parameter
- Cloud profile setting
"""
return __opts__.get(
"location",
config.get_cloud_config_value(
"location",
vm_ or get_configured_provider(),
__opts__,
# default=DEFAULT_LOCATION,
search_global=False,
),
)
def create(vm_):
"""
Create a single VM from a data dict
"""
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 "softlayer",
vm_["profile"],
vm_=vm_,
)
is False
):
return False
except AttributeError:
pass
name = vm_["name"]
hostname = name
domain = config.get_cloud_config_value("domain", vm_, __opts__, default=None)
if domain is None:
raise SaltCloudSystemExit("A domain name is required for the SoftLayer driver.")
if vm_.get("use_fqdn"):
name = ".".join([name, domain])
vm_["name"] = name
__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 Cloud VM %s", name)
conn = get_conn()
kwargs = {
"hostname": hostname,
"domain": domain,
"startCpus": vm_["cpu_number"],
"maxMemory": vm_["ram"],
"hourlyBillingFlag": vm_["hourly_billing"],
}
local_disk_flag = config.get_cloud_config_value(
"local_disk", vm_, __opts__, default=False
)
kwargs["localDiskFlag"] = local_disk_flag
if "image" in vm_:
kwargs["operatingSystemReferenceCode"] = vm_["image"]
kwargs["blockDevices"] = []
disks = vm_["disk_size"]
if isinstance(disks, int):
disks = [str(disks)]
elif isinstance(disks, str):
disks = [size.strip() for size in disks.split(",")]
count = 0
for disk in disks:
# device number '1' is reserved for the SWAP disk
if count == 1:
count += 1
block_device = {
"device": str(count),
"diskImage": {"capacity": str(disk)},
}
kwargs["blockDevices"].append(block_device)
count += 1
# Upper bound must be 5 as we're skipping '1' for the SWAP disk ID
if count > 5:
log.warning(
"More that 5 disks were specified for %s ."
"The first 5 disks will be applied to the VM, "
"but the remaining disks will be ignored.\n"
"Please adjust your cloud configuration to only "
"specify a maximum of 5 disks.",
name,
)
break
elif "global_identifier" in vm_:
kwargs["blockDeviceTemplateGroup"] = {
"globalIdentifier": vm_["global_identifier"]
}
location = get_location(vm_)
if location:
kwargs["datacenter"] = {"name": location}
private_vlan = config.get_cloud_config_value(
"private_vlan", vm_, __opts__, default=False
)
if private_vlan:
kwargs["primaryBackendNetworkComponent"] = {"networkVlan": {"id": private_vlan}}
private_network = config.get_cloud_config_value(
"private_network", vm_, __opts__, default=False
)
if bool(private_network) is True:
kwargs["privateNetworkOnlyFlag"] = "True"
public_vlan = config.get_cloud_config_value(
"public_vlan", vm_, __opts__, default=False
)
if public_vlan:
kwargs["primaryNetworkComponent"] = {"networkVlan": {"id": public_vlan}}
public_security_groups = config.get_cloud_config_value(
"public_security_groups", vm_, __opts__, default=False
)
if public_security_groups:
secgroups = [
{"securityGroup": {"id": int(sg)}} for sg in public_security_groups
]
pnc = kwargs.get("primaryNetworkComponent", {})
pnc["securityGroupBindings"] = secgroups
kwargs.update({"primaryNetworkComponent": pnc})
private_security_groups = config.get_cloud_config_value(
"private_security_groups", vm_, __opts__, default=False
)
if private_security_groups:
secgroups = [
{"securityGroup": {"id": int(sg)}} for sg in private_security_groups
]
pbnc = kwargs.get("primaryBackendNetworkComponent", {})
pbnc["securityGroupBindings"] = secgroups
kwargs.update({"primaryBackendNetworkComponent": pbnc})
max_net_speed = config.get_cloud_config_value(
"max_net_speed", vm_, __opts__, default=10
)
if max_net_speed:
kwargs["networkComponents"] = [{"maxSpeed": int(max_net_speed)}]
post_uri = config.get_cloud_config_value("post_uri", vm_, __opts__, default=None)
if post_uri:
kwargs["postInstallScriptUri"] = post_uri
dedicated_host_id = config.get_cloud_config_value(
"dedicated_host_id", vm_, __opts__, default=None
)
if dedicated_host_id:
kwargs["dedicatedHost"] = {"id": dedicated_host_id}
__utils__["cloud.fire_event"](
"event",
"requesting instance",
f"salt/cloud/{name}/requesting",
args={
"kwargs": __utils__["cloud.filter_event"](
"requesting", kwargs, list(kwargs)
),
},
sock_dir=__opts__["sock_dir"],
transport=__opts__["transport"],
)
try:
response = conn.createObject(kwargs)
except Exception as exc: # pylint: disable=broad-except
log.error(
"Error creating %s on SoftLayer\n\n"
"The following exception was thrown when trying to "
"run the initial deployment: \n%s",
name,
exc,
# Show the traceback if the debug logging level is enabled
exc_info_on_loglevel=logging.DEBUG,
)
return False
ip_type = "primaryIpAddress"
private_ssh = config.get_cloud_config_value(
"private_ssh", vm_, __opts__, default=False
)
private_wds = config.get_cloud_config_value(
"private_windows", vm_, __opts__, default=False
)
if private_ssh or private_wds or public_vlan is None:
ip_type = "primaryBackendIpAddress"
def wait_for_ip():
"""
Wait for the IP address to become available
"""
nodes = list_nodes_full()
if ip_type in nodes[hostname]:
return nodes[hostname][ip_type]
time.sleep(1)
return False
ip_address = salt.utils.cloud.wait_for_fun(
wait_for_ip,
timeout=config.get_cloud_config_value(
"wait_for_fun_timeout", vm_, __opts__, default=15 * 60
),
)
if config.get_cloud_config_value("deploy", vm_, __opts__) is not True:
return show_instance(hostname, call="action")
SSH_PORT = 22
WINDOWS_DS_PORT = 445
managing_port = SSH_PORT
if config.get_cloud_config_value(
"windows", vm_, __opts__
) or config.get_cloud_config_value("win_installer", vm_, __opts__):
managing_port = WINDOWS_DS_PORT
ssh_connect_timeout = config.get_cloud_config_value(
"ssh_connect_timeout", vm_, __opts__, 15 * 60
)
connect_timeout = config.get_cloud_config_value(
"connect_timeout", vm_, __opts__, ssh_connect_timeout
)
if not salt.utils.cloud.wait_for_port(
ip_address, port=managing_port, timeout=connect_timeout
):
raise SaltCloudSystemExit("Failed to authenticate against remote ssh")
pass_conn = get_conn(service="SoftLayer_Account")
mask = {
"virtualGuests": {"powerState": "", "operatingSystem": {"passwords": ""}},
}
def get_credentials():
"""
Wait for the password to become available
"""
node_info = pass_conn.getVirtualGuests(id=response["id"], mask=mask)
for node in node_info:
if (
node["id"] == response["id"]
and "passwords" in node["operatingSystem"]
and node["operatingSystem"]["passwords"]
):
return (
node["operatingSystem"]["passwords"][0]["username"],
node["operatingSystem"]["passwords"][0]["password"],
)
time.sleep(5)
return False
username, passwd = salt.utils.cloud.wait_for_fun( # pylint: disable=W0633
get_credentials,
timeout=config.get_cloud_config_value(
"wait_for_fun_timeout", vm_, __opts__, default=15 * 60
),
)
response["username"] = username
response["password"] = passwd
response["public_ip"] = ip_address
ssh_username = config.get_cloud_config_value(
"ssh_username", vm_, __opts__, default=username
)
vm_["ssh_host"] = ip_address
vm_["password"] = passwd
ret = __utils__["cloud.bootstrap"](vm_, __opts__)
ret.update(response)
__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(mask="mask[id]", call=None):
"""
Return a list of the VMs that are on the provider
"""
if call == "action":
raise SaltCloudSystemExit(
"The list_nodes_full function must be called with -f or --function."
)
ret = {}
conn = get_conn(service="SoftLayer_Account")
response = conn.getVirtualGuests()
for node_id in response:
hostname = node_id["hostname"]
ret[hostname] = node_id
__utils__["cloud.cache_node_list"](
ret, _get_active_provider_name().split(":")[0], __opts__
)
return ret
def list_nodes(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."
)
ret = {}
nodes = list_nodes_full()
if "error" in nodes:
raise SaltCloudSystemExit(
"An error occurred while listing nodes: {}".format(
nodes["error"]["Errors"]["Error"]["Message"]
)
)
for node in nodes:
ret[node] = {
"id": nodes[node]["hostname"],
"ram": nodes[node]["maxMemory"],
"cpus": nodes[node]["maxCpu"],
}
if "primaryIpAddress" in nodes[node]:
ret[node]["public_ips"] = nodes[node]["primaryIpAddress"]
if "primaryBackendIpAddress" in nodes[node]:
ret[node]["private_ips"] = nodes[node]["primaryBackendIpAddress"]
if "status" in nodes[node]:
ret[node]["state"] = str(nodes[node]["status"]["name"])
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 show_instance(name, call=None):
"""
Show the details from SoftLayer concerning a guest
"""
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], _get_active_provider_name(), __opts__)
return nodes[name]
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."
)
__utils__["cloud.fire_event"](
"event",
"destroying instance",
f"salt/cloud/{name}/destroying",
args={"name": name},
sock_dir=__opts__["sock_dir"],
transport=__opts__["transport"],
)
node = show_instance(name, call="action")
conn = get_conn()
response = conn.deleteObject(id=node["id"])
__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 response
def list_vlans(call=None):
"""
List all VLANs associated with the account
"""
if call != "function":
raise SaltCloudSystemExit(
"The list_vlans function must be called with -f or --function."
)
conn = get_conn(service="SoftLayer_Account")
return conn.getNetworkVlans()
Zerion Mini Shell 1.0