Source code for bowl.api

"""
This module is the web server for running the REST API of bowl.

Created on 14 March 2014
@author: Charlie Lewis
"""
import ast
import datetime
import os
import sys
import tarfile
import web

from bowl.cli_opts import add
from bowl.cli_opts import connect
from bowl.cli_opts import delete
from bowl.cli_opts import disconnect
from bowl.cli_opts import grant
from bowl.cli_opts import hosts
from bowl.cli_opts import image_import
from bowl.cli_opts import images
from bowl.cli_opts import info
from bowl.cli_opts import kill
from bowl.cli_opts import link
from bowl.cli_opts import list
from bowl.cli_opts import login
from bowl.cli_opts import logout
from bowl.cli_opts import logs
from bowl.cli_opts import new
from bowl.cli_opts import remove
from bowl.cli_opts import repositories
from bowl.cli_opts import revoke
from bowl.cli_opts import services
from bowl.cli_opts import snapshot
from bowl.cli_opts import snapshots
from bowl.cli_opts import subtract
from bowl.cli_opts import test
from bowl.cli_opts import unlink
#from bowl.cli_opts import update
from bowl.cli_opts import version

START_TIME = datetime.datetime.now()
URLS = ()

[docs]class main(object): """ This class is responsible for initializing the urls and web server. """ # need __new__ for tests, but fails to call __init__ when actually running def __new__(*args, **kw): if hasattr(sys, '_called_from_test'): print "don't call __init__" else: # pragma: no cover return object.__new__(*args, **kw) def __init__(self, port=8080, host="0.0.0.0"): # pragma: no cover urls = self.setup() app = web.application(urls, globals()) web.httpserver.runsimple(app.wsgifunc(), (host, port))
[docs] def setup(self): global URLS urls = ( '/', 'root', '/add', 'api_add', '/connect/(.*)', 'api_connect', '/delete/(.*)', 'api_delete', '/disconnect/(.*)', 'api_disconnect', '/grant/(.*)/(.*)', 'api_grant', '/hosts', 'api_hosts', '/images', 'api_images', '/import', 'api_image_import', '/info', 'api_info', '/kill/(.*)', 'api_kill', '/link/(.*)', 'api_link', '/list', 'api_list', '/login', 'api_login', '/logout', 'api_logout', '/logs/(.*)', 'api_logs', '/new', 'api_new', '/remove', 'api_remove', '/repositories', 'api_repositories', '/repo/services', 'api_repo_services', '/revoke/(.*)/(.*)', 'api_revoke', '/services', 'api_services', '/snapshot/(.*)', 'api_snapshot', '/snapshots', 'api_snapshots', '/subtract/(.*)/(.*)/(.*)/(.*)', 'api_subtract', '/test', 'api_test', '/unlink/(.*)', 'api_unlink', '/uptime', 'api_uptime', '/version', 'api_version', ) URLS = urls return urls
[docs]class root: """ This class is resposible for giving information about the rest server. """
[docs] def GET(self): """ GETs the information about the rest server and renders it. :return: returns the information """ class Object(object): pass args = Object() args.z = True string = version.version.main(args) string += "\n\n" global URLS i = 0 for url in URLS: if i % 2 == 0: string += url+"\n" i += 1 return string
[docs]class api_add: """ This class is resposible for adding a service """
[docs] def POST(self): """ POSTs the new service being added. """ # !! TODO return ""
[docs]class api_connect: """ This class is resposible for creating a connection to a docker host. """
[docs] def GET(self, host): """ creates a connection to a new docker host. """ # !! TODO validate host class Object(object): pass args = Object() args.DOCKER_HOST = host # !! TODO figure out a way to make this an option args.metadata_path = "~/.bowl" return connect.connect.main(args)
[docs]class api_delete: """ This class is resposible for deleting an image. """
[docs] def GET(self, image): """ deletes the specified image. """ class Object(object): pass args = Object() args.IMAGE_NAME = image # !! TODO figure out a way to make this an option args.metadata_path = "~/.bowl" return delete.delete.main(args)
[docs]class api_disconnect: """ This class is resposible for disconnecting a connection to a docker host. """
[docs] def GET(self, host): """ disconnects the specified docker host. """ # !! TODO validate host class Object(object): pass args = Object() args.DOCKER_HOST = host # !! TODO figure out a way to make this an option args.metadata_path = "~/.bowl" return disconnect.disconnect.main(args)
[docs]class api_grant: """ This class is resposible for granting access to a container for a user. """
[docs] def GET(self, user, container): """ grants access for the specified user to a container. """ # !! TODO return
[docs]class api_hosts: """ This class is resposible for listing the connected docker hosts. """
[docs] def GET(self): """ GETs the connected docker hosts. :return: returns the list of connected docker hosts. """ class Object(object): pass args = Object() args.z = True # !! TODO figure out a way to make this an option args.metadata_path = "~/.bowl" return hosts.hosts.main(args)
[docs]class api_image_import: """ This class is resposible for importing an image. """
[docs] def POST(self): """ POSTs the image being imported. """ # !! TODO return ""
[docs]class api_images: """ This class is resposible for listing the images. """
[docs] def GET(self): """ GETs the images. :return: returns the list of images. """ class Object(object): pass args = Object() args.z = True # !! TODO figure out a way to make this an option args.metadata_path = "~/.bowl" return images.images.main(args)
[docs]class api_info: """ This class is resposible for giving system-wide information. """
[docs] def GET(self): """ GETs the system-wide information and renders it. :return: returns the information. """ class Object(object): pass args = Object() args.z = True # !! TODO figure out a way to make this an option args.metadata_path = "~/.bowl" return info.info.main(args)
[docs]class api_kill: """ This class is resposible for killing a container. """
[docs] def GET(self, container): """ the container to kill. """ class Object(object): pass args = Object() args.z = True args.CONTAINER = container # !! TODO figure out a way to make this an option args.metadata_path = "~/.bowl" return kill.kill.main(args)
[docs]class api_list: """ This class is resposible for listing the running containers. """
[docs] def GET(self): """ GETs the list of running containers. :return: returns the list of running containers. """ class Object(object): pass args = Object() args.z = True # !! TODO figure out a way to make this an option args.metadata_path = "~/.bowl" return list.list.main(args)
[docs]class api_login: """ This class is resposible for logging in. """
[docs] def POST(self): """ POSTs the user to login. """ # !! TODO return ""
[docs]class api_logout: """ This class is resposible for logging out. """
[docs] def POST(self): """ POSTs the user to logout. """ # !! TODO return ""
[docs]class api_logs: """ This class is resposible for returning logs of a server. """
[docs] def GET(self, container): """ GETs the logs of a server. :return: returns the logs of a server. """ class Object(object): pass args = Object() args.z = True args.CONTAINER = container # !! TODO figure out a way to make this an option args.metadata_path = "~/.bowl" return logs.logs.main(args)
[docs]class api_new: """ This class is resposible for creating a new container. """ def __init__(self): self.data = "" self.fullpath = "" try: self.data = web.data() self.fullpath = web.ctx['fullpath'] except: print "failure"
[docs] def POST(self): """ POSTs the creation of a new container. """ class Object(object): pass args = Object() args.no_curses = True # !! TODO fix me args.metadata_path = "~/.bowl" try: self.data = ast.literal_eval(self.data) if "host" in self.data: args.host = [] args.host.append(self.data['host']) else: return "must specify a host to run the container on" if "service" in self.data: args.service = [] args.service.append(self.data['service']) else: args.service = False if "image" in self.data: args.image = self.data['image'] else: args.image = False if not args.image and not args.service: return "must specify a service or image to run" if "command" in self.data: args.command = self.data['command'] else: args.command = False if "entrypoint" in self.data: args.entrypoint = self.data['entrypoint'] else: args.entrypoint = False if "volume" in self.data: args.volume = self.data['volume'] else: args.volume = False if "port" in self.data: args.port = self.data['port'] else: args.port = False if "link" in self.data: args.link = self.data['link'] else: args.link = False if "name" in self.data: args.name = self.data['name'] else: args.name = False if "unique" in self.data: args.unique = self.data['unique'] else: args.unique = False if "user" in self.data: args.user = self.data['user'] else: args.user = False return new.new.main(args) except: return "failure" return
[docs]class api_remove: """ This class is resposible for removing a container. """
[docs] def POST(self): """ POSTs the removal of a container. """ class Object(object): pass args = Object() args.z = True # !! TODO figure out a way to make this an option args.metadata_path = "~/.bowl" # !! TODO return ""
[docs]class api_repositories: """ This class is resposible for listing the connected repositories. """
[docs] def GET(self): """ GETs the connected repositories. :return: returns the list of connected repositories. """ class Object(object): pass args = Object() args.z = True # !! TODO figure out a way to make this an option args.metadata_path = "~/.bowl" return repositories.repositories.main(args)
[docs]class api_repo_services: """ This class is resposible for sending services to the client. """
[docs] def make_tarfile(self, output_filename, source_dir): with tarfile.open(output_filename, "w:gz") as tar: tar.add(source_dir, arcname=os.path.basename(source_dir))
[docs] def GET(self): """ GETs the services and packages them up and serves them up as a static file. """ # !! TODO also need to add non-default services cwd = os.path.dirname(__file__) self.make_tarfile(os.path.join(cwd, "static/default.tar.gz"), os.path.join(cwd, "containers/.default")) f = open(os.path.join(cwd, "static/default.tar.gz"), 'r') return f.read()
[docs]class api_revoke: """ This class is resposible for revokeing access to a container for a user. """
[docs] def GET(self, user, container): """ revokes access for the specified user to a container. """ # !! TODO return
[docs]class api_services: """ This class is resposible for listing services. """
[docs] def GET(self): """ GETs the list of services. """ class Object(object): pass args = Object() args.z = True args.json = True args.quiet = False # !! TODO figure out a way to make this an option args.metadata_path = "~/.bowl" return services.services.main(args)
[docs]class api_snapshot: """ This class is resposible for snapshotting a container. """
[docs] def GET(self, container): """ creates a snapshot of a container. """ class Object(object): pass args = Object() args.CONTAINER = container args.z = True # !! TODO figure out a way to make this an option args.metadata_path = "~/.bowl" return snapshot.snapshot.main(args)
[docs]class api_snapshots: """ This class is resposible for listing snapshots. """
[docs] def GET(self): """ GETs the list of snapshots. """ class Object(object): pass args = Object() args.z = True # !! TODO figure out a way to make this an option args.metadata_path = "~/.bowl" return snapshots.snapshots.main(args)
[docs]class api_subtract: """ This class is resposible for subtracting services. """
[docs] def GET(self, os, version, service_type, name): """ GETs the service to subtract. """ class Object(object): pass args = Object() args.z = True # !! TODO figure out a way to make this an option args.metadata_path = "~/.bowl" args.OS = os args.VERSION = version args.TYPE = service_type args.NAME = name return subtract.subtract.main(args)
[docs]class api_test: """ This class is resposible for running the tests. """
[docs] def GET(self): """ runs the tests. """ # !! TODO return ""
[docs]class api_uptime: """ This class is resposible for returning the uptime of the API server. """
[docs] def GET(self): """ GETs the uptime of the API server. :return: returns the uptime of the API server. """ uptime_seconds = (datetime.datetime.now()-START_TIME).total_seconds() uptime_string = str(datetime.timedelta(seconds = uptime_seconds)) return uptime_string
[docs]class api_version: """ This class is resposible for returning the version of bowl. """
[docs] def GET(self): """ GETs the version of bowl. :return: returns the version of bowl. """ class Object(object): pass args = Object() args.z = True return version.version.main(args)
if __name__ == "__main__": # pragma: no cover main().app.run()