Mini Shell
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import base64
try:
import simplejson as json
except Exception:
import json
from libcloud.utils.py3 import httplib, urlparse
from libcloud.utils.py3 import b
from libcloud.common.base import JsonResponse, ConnectionUserAndKey
from libcloud.container.base import (Container, ContainerDriver,
ContainerImage)
from libcloud.container.providers import Provider
from libcloud.container.types import ContainerState
VALID_RESPONSE_CODES = [httplib.OK, httplib.ACCEPTED, httplib.CREATED,
httplib.NO_CONTENT]
class RancherResponse(JsonResponse):
def parse_error(self):
parsed = super(RancherResponse, self).parse_error()
if 'fieldName' in parsed:
return "Field %s is %s: %s - %s" % (parsed['fieldName'],
parsed['code'],
parsed['message'],
parsed['detail'])
else:
return "%s - %s" % (parsed['message'], parsed['detail'])
def success(self):
return self.status in VALID_RESPONSE_CODES
class RancherException(Exception):
def __init__(self, code, message):
self.code = code
self.message = message
self.args = (code, message)
def __str__(self):
return "%s %s" % (self.code, self.message)
def __repr__(self):
return "RancherException %s %s" % (self.code, self.message)
class RancherConnection(ConnectionUserAndKey):
responseCls = RancherResponse
timeout = 30
def add_default_headers(self, headers):
"""
Add parameters that are necessary for every request
If user and password are specified, include a base http auth
header
"""
headers['Content-Type'] = 'application/json'
headers['Accept'] = 'application/json'
if self.key and self.user_id:
user_b64 = base64.b64encode(b('%s:%s' % (self.user_id, self.key)))
headers['Authorization'] = 'Basic %s' % (user_b64.decode('utf-8'))
return headers
class RancherContainerDriver(ContainerDriver):
"""
Driver for Rancher by Rancher Labs.
This driver is capable of interacting with the Version 1 API of Rancher.
It currently does NOT support the Version 2 API.
Example:
>>> from libcloud.container.providers import get_driver
>>> from libcloud.container.types import Provider
>>> driver = get_driver(Provider.RANCHER)
>>> connection = driver(key="ACCESS_KEY_HERE",
secret="SECRET_KEY_HERE", host="172.30.0.100", port=8080)
>>> image = ContainerImage("hastebin", "hastebin", "rlister/hastebin",
"latest", driver=None)
>>> newcontainer = connection.deploy_container("myawesomepastebin",
image, environment={"STORAGE_TYPE": "file"})
:ivar baseuri: The URL base path to the API.
:type baseuri: ``str``
"""
type = Provider.RANCHER
name = 'Rancher'
website = 'http://rancher.com'
connectionCls = RancherConnection
# Holding off on cluster support for now.
# Only Environment API interaction enabled.
supports_clusters = False
# As in the /v1/
version = '1'
def __init__(self, key, secret, secure=True, host='localhost', port=443):
"""
Creates a new Rancher Container driver.
:param key: API key or username to used (required)
:type key: ``str``
:param secret: Secret password to be used (required)
:type secret: ``str``
:param secure: Whether to use HTTPS or HTTP.
:type secure: ``bool``
:param host: Override hostname used for connections. This can also
be a full URL string, including scheme, port, and base path.
:type host: ``str``
:param port: Override port used for connections.
:type port: ``int``
:return: A newly initialized driver instance.
"""
# Parse the Given Host
if '://' not in host and not host.startswith("//"):
host = '//' + host
parsed = urlparse.urlparse(host)
super(RancherContainerDriver, self).__init__(
key=key,
secret=secret,
secure=False if parsed.scheme == 'http' else secure,
host=parsed.hostname,
port=parsed.port if parsed.port else port
)
self.baseuri = parsed.path if parsed.path else "/v%s" % self.version
def ex_list_stacks(self):
"""
List all Rancher Stacks
http://docs.rancher.com/rancher/v1.2/en/api/api-resources/environment/
:rtype: ``list`` of ``dict``
"""
result = self.connection.request(
"%s/environments" % self.baseuri).object
return result['data']
def ex_deploy_stack(self, name, description=None, docker_compose=None,
environment=None, external_id=None,
rancher_compose=None, start=True):
"""
Deploy a new stack.
http://docs.rancher.com/rancher/v1.2/en/api/api-resources/environment/#create
:param name: The desired name of the stack. (required)
:type name: ``str``
:param description: A desired description for the stack.
:type description: ``str``
:param docker_compose: The Docker Compose configuration to use.
:type docker_compose: ``str``
:param environment: Environment K/V specific to this stack.
:type environment: ``dict``
:param external_id: The externalId of the stack.
:type external_id: ``str``
:param rancher_compose: The Rancher Compose configuration for this env.
:type rancher_compose: ``str``
:param start: Whether to start this stack on creation.
:type start: ``bool``
:return: The newly created stack.
:rtype: ``dict``
"""
payload = {
"description": description,
"dockerCompose": docker_compose,
"environment": environment,
"externalId": external_id,
"name": name,
"rancherCompose": rancher_compose,
"startOnCreate": start
}
data = json.dumps(dict((k, v) for (k, v) in payload.items()
if v is not None))
result = self.connection.request('%s/environments' %
self.baseuri, data=data,
method='POST').object
return result
def ex_get_stack(self, env_id):
"""
Get a stack by ID
:param env_id: The stack to be obtained.
:type env_id: ``str``
:rtype: ``dict``
"""
result = self.connection.request("%s/environments/%s" %
(self.baseuri, env_id)).object
return result
def ex_search_stacks(self, search_params):
"""
Search for stacks matching certain filters
i.e. ``{ "name": "awesomestack"}``
:param search_params: A collection of search parameters to use.
:type search_params: ``dict``
:rtype: ``list``
"""
search_list = []
for f, v in search_params.items():
search_list.append(f + '=' + v)
search_items = '&'.join(search_list)
result = self.connection.request("%s/environments?%s" % (
self.baseuri, search_items)).object
return result['data']
def ex_destroy_stack(self, env_id):
"""
Destroy a stack by ID
http://docs.rancher.com/rancher/v1.2/en/api/api-resources/environment/#delete
:param env_id: The stack to be destroyed.
:type env_id: ``str``
:return: True if destroy was successful, False otherwise.
:rtype: ``bool``
"""
result = self.connection.request('%s/environments/%s' % (
self.baseuri, env_id),
method='DELETE')
return result.status in VALID_RESPONSE_CODES
def ex_activate_stack(self, env_id):
"""
Activate Services for a stack.
http://docs.rancher.com/rancher/v1.2/en/api/api-resources/environment/#activateservices
:param env_id: The stack to activate services for.
:type env_id: ``str``
:return: True if activate was successful, False otherwise.
:rtype: ``bool``
"""
result = self.connection.request(
'%s/environments/%s?action=activateservices' % (
self.baseuri, env_id), method='POST'
)
return result.status in VALID_RESPONSE_CODES
def ex_deactivate_stack(self, env_id):
"""
Deactivate Services for a stack.
http://docs.rancher.com/rancher/v1.2/en/api/api-resources/environment/#deactivateservices
:param env_id: The stack to deactivate services for.
:type env_id: ``str``
:return: True if deactivate was successful, False otherwise.
:rtype: ``bool``
"""
result = self.connection.request(
'%s/environments/%s?action=deactivateservices' % (
self.baseuri, env_id), method='POST'
)
return result.status in VALID_RESPONSE_CODES
def ex_list_services(self):
"""
List all Rancher Services
http://docs.rancher.com/rancher/v1.2/en/api/api-resources/service/
:rtype: ``list`` of ``dict``
"""
result = self.connection.request("%s/services" % self.baseuri).object
return result['data']
def ex_deploy_service(self, name, image, environment_id,
start=True, assign_service_ip_address=None,
service_description=None, external_id=None,
metadata=None, retain_ip=None, scale=None,
scale_policy=None, secondary_launch_configs=None,
selector_container=None, selector_link=None,
vip=None, **launch_conf):
"""
Deploy a Rancher Service under a stack.
http://docs.rancher.com/rancher/v1.2/en/api/api-resources/service/#create
*Any further configuration passed applies to the ``launchConfig``*
:param name: The desired name of the service. (required)
:type name: ``str``
:param image: The Image object to deploy. (required)
:type image: :class:`libcloud.container.base.ContainerImage`
:param environment_id: The stack ID this service is tied to. (required)
:type environment_id: ``str``
:param start: Whether to start the service on creation.
:type start: ``bool``
:param assign_service_ip_address: The IP address to assign the service.
:type assign_service_ip_address: ``bool``
:param service_description: The service description.
:type service_description: ``str``
:param external_id: The externalId for this service.
:type external_id: ``str``
:param metadata: K/V Metadata for this service.
:type metadata: ``dict``
:param retain_ip: Whether this service should retain its IP.
:type retain_ip: ``bool``
:param scale: The scale of containers in this service.
:type scale: ``int``
:param scale_policy: The scaling policy for this service.
:type scale_policy: ``dict``
:param secondary_launch_configs: Secondary container launch configs.
:type secondary_launch_configs: ``list``
:param selector_container: The selectorContainer for this service.
:type selector_container: ``str``
:param selector_link: The selectorLink for this service.
:type selector_link: ``type``
:param vip: The VIP to assign to this service.
:type vip: ``str``
:return: The newly created service.
:rtype: ``dict``
"""
launch_conf['imageUuid'] = self._degen_image(image),
service_payload = {
"assignServiceIpAddress": assign_service_ip_address,
"description": service_description,
"environmentId": environment_id,
"externalId": external_id,
"launchConfig": launch_conf,
"metadata": metadata,
"name": name,
"retainIp": retain_ip,
"scale": scale,
"scalePolicy": scale_policy,
"secondary_launch_configs": secondary_launch_configs,
"selectorContainer": selector_container,
"selectorLink": selector_link,
"startOnCreate": start,
"vip": vip
}
data = json.dumps(dict((k, v) for (k, v) in service_payload.items()
if v is not None))
result = self.connection.request('%s/services' % self.baseuri,
data=data, method='POST').object
return result
def ex_get_service(self, service_id):
"""
Get a service by ID
:param service_id: The service_id to be obtained.
:type service_id: ``str``
:rtype: ``dict``
"""
result = self.connection.request("%s/services/%s" %
(self.baseuri, service_id)).object
return result
def ex_search_services(self, search_params):
"""
Search for services matching certain filters
i.e. ``{ "name": "awesomesause", "environmentId": "1e2"}``
:param search_params: A collection of search parameters to use.
:type search_params: ``dict``
:rtype: ``list``
"""
search_list = []
for f, v in search_params.items():
search_list.append(f + '=' + v)
search_items = '&'.join(search_list)
result = self.connection.request("%s/services?%s" % (
self.baseuri, search_items)).object
return result['data']
def ex_destroy_service(self, service_id):
"""
Destroy a service by ID
http://docs.rancher.com/rancher/v1.2/en/api/api-resources/service/#delete
:param service_id: The service to be destroyed.
:type service_id: ``str``
:return: True if destroy was successful, False otherwise.
:rtype: ``bool``
"""
result = self.connection.request('%s/services/%s' % (self.baseuri,
service_id), method='DELETE')
return result.status in VALID_RESPONSE_CODES
def ex_activate_service(self, service_id):
"""
Activate a service.
http://docs.rancher.com/rancher/v1.2/en/api/api-resources/service/#activate
:param service_id: The service to activate services for.
:type service_id: ``str``
:return: True if activate was successful, False otherwise.
:rtype: ``bool``
"""
result = self.connection.request('%s/services/%s?action=activate' %
(self.baseuri, service_id),
method='POST')
return result.status in VALID_RESPONSE_CODES
def ex_deactivate_service(self, service_id):
"""
Deactivate a service.
http://docs.rancher.com/rancher/v1.2/en/api/api-resources/service/#deactivate
:param service_id: The service to deactivate services for.
:type service_id: ``str``
:return: True if deactivate was successful, False otherwise.
:rtype: ``bool``
"""
result = self.connection.request('%s/services/%s?action=deactivate' %
(self.baseuri, service_id),
method='POST')
return result.status in VALID_RESPONSE_CODES
def list_containers(self):
"""
List the deployed containers.
http://docs.rancher.com/rancher/v1.2/en/api/api-resources/container/
:rtype: ``list`` of :class:`libcloud.container.base.Container`
"""
result = self.connection.request("%s/containers" % self.baseuri).object
containers = [self._to_container(value) for value in result['data']]
return containers
def deploy_container(self, name, image, parameters=None, start=True,
**config):
"""
Deploy a new container.
http://docs.rancher.com/rancher/v1.2/en/api/api-resources/container/#create
**The following is the Image format used for ``ContainerImage``**
*For a ``imageuuid``*:
- ``docker:<hostname>:<port>/<namespace>/<imagename>:<version>``
*The following applies*:
- ``id`` = ``<imagename>``
- ``name`` = ``<imagename>``
- ``path`` = ``<hostname>:<port>/<namespace>/<imagename>``
- ``version`` = ``<version>``
*Any extra configuration can also be passed i.e. "environment"*
:param name: The desired name of the container. (required)
:type name: ``str``
:param image: The Image object to deploy. (required)
:type image: :class:`libcloud.container.base.ContainerImage`
:param parameters: Container Image parameters (unused)
:type parameters: ``str``
:param start: Whether to start the container on creation(startOnCreate)
:type start: ``bool``
:rtype: :class:`Container`
"""
payload = {
"name": name,
"imageUuid": self._degen_image(image),
"startOnCreate": start,
}
config.update(payload)
data = json.dumps(config)
result = self.connection.request('%s/containers' % self.baseuri,
data=data, method='POST').object
return self._to_container(result)
def get_container(self, con_id):
"""
Get a container by ID
:param con_id: The ID of the container to get
:type con_id: ``str``
:rtype: :class:`libcloud.container.base.Container`
"""
result = self.connection.request("%s/containers/%s" %
(self.baseuri, con_id)).object
return self._to_container(result)
def start_container(self, container):
"""
Start a container
:param container: The container to be started
:type container: :class:`libcloud.container.base.Container`
:return: The container refreshed with current data
:rtype: :class:`libcloud.container.base.Container`
"""
result = self.connection.request('%s/containers/%s?action=start' %
(self.baseuri, container.id),
method='POST').object
return self._to_container(result)
def stop_container(self, container):
"""
Stop a container
:param container: The container to be stopped
:type container: :class:`libcloud.container.base.Container`
:return: The container refreshed with current data
:rtype: :class:`libcloud.container.base.Container`
"""
result = self.connection.request('%s/containers/%s?action=stop' %
(self.baseuri, container.id),
method='POST').object
return self._to_container(result)
def ex_search_containers(self, search_params):
"""
Search for containers matching certain filters
i.e. ``{ "imageUuid": "docker:mysql", "state": "running"}``
:param search_params: A collection of search parameters to use.
:type search_params: ``dict``
:rtype: ``list``
"""
search_list = []
for f, v in search_params.items():
search_list.append(f + '=' + v)
search_items = '&'.join(search_list)
result = self.connection.request("%s/containers?%s" % (
self.baseuri, search_items)).object
return result['data']
def destroy_container(self, container):
"""
Remove a container
:param container: The container to be destroyed
:type container: :class:`libcloud.container.base.Container`
:return: True if the destroy was successful, False otherwise.
:rtype: ``bool``
"""
result = self.connection.request('%s/containers/%s' % (self.baseuri,
container.id), method='DELETE').object
return self._to_container(result)
def _gen_image(self, imageuuid):
"""
This function converts a valid Rancher ``imageUuid`` string to a valid
image object. Only supports docker based images hence `docker:` must
prefix!!
Please see the deploy_container() for details on the format.
:param imageuuid: A valid Rancher image string
i.e. ``docker:rlister/hastebin:8.0``
:type imageuuid: ``str``
:return: Converted ContainerImage object.
:rtype: :class:`libcloud.container.base.ContainerImage`
"""
# Obtain just the name(:version) for parsing
if '/' not in imageuuid:
# String looks like `docker:mysql:8.0`
image_name_version = imageuuid.partition(':')[2]
else:
# String looks like `docker:oracle/mysql:8.0`
image_name_version = imageuuid.rpartition("/")[2]
# Parse based on ':'
if ':' in image_name_version:
version = image_name_version.partition(":")[2]
id = image_name_version.partition(":")[0]
name = id
else:
version = 'latest'
id = image_name_version
name = id
# Get our path based on if there was a version
if version != 'latest':
path = imageuuid.partition(':')[2].rpartition(':')[0]
else:
path = imageuuid.partition(':')[2]
return ContainerImage(
id=id,
name=name,
path=path,
version=version,
driver=self.connection.driver,
extra={
"imageUuid": imageuuid
}
)
def _degen_image(self, image):
"""
Take in an image object to break down into an ``imageUuid``
:param image:
:return:
"""
# Only supporting docker atm
image_type = "docker"
if image.version is not None:
return image_type + ':' + image.path + ':' + image.version
else:
return image_type + ':' + image.path
def _to_container(self, data):
"""
Convert container in proper Container instance object
** Updating is NOT supported!!
:param data: API data about container i.e. result.object
:return: Proper Container object:
see http://libcloud.readthedocs.io/en/latest/container/api.html
"""
rancher_state = data['state']
# A Removed container is purged after x amt of time.
# Both of these render the container dead (can't be started later)
terminate_condition = ["removed", "purged"]
if 'running' in rancher_state:
state = ContainerState.RUNNING
elif 'stopped' in rancher_state:
state = ContainerState.STOPPED
elif 'restarting' in rancher_state:
state = ContainerState.REBOOTING
elif 'error' in rancher_state:
state = ContainerState.ERROR
elif any(x in rancher_state for x in terminate_condition):
state = ContainerState.TERMINATED
elif data['transitioning'] == 'yes':
# Best we can do for current actions
state = ContainerState.PENDING
else:
state = ContainerState.UNKNOWN
# Everything contained in the json response is dumped in extra
extra = data
return Container(
id=data['id'],
name=data['name'],
image=self._gen_image(data['imageUuid']),
ip_addresses=[data['primaryIpAddress']],
state=state,
driver=self.connection.driver,
extra=extra)
Zerion Mini Shell 1.0