initialise repo
[debian/make-magic.git] / lib / httpd.py
1 #! /usr/bin/env python
2
3 '''mudpuppy httpd API interface
4
5 This provides a very, very lightweight WSGI based http server 
6 to expose a JSON based API.
7
8 This is by no means the only way to interact with make magic
9 from the outside world, but it is a convenient way to make a
10 solid point of demarcation, and give something like mudpuppy
11 something to talk to.
12
13 There is NO user security implemented here. If you want some
14 (and you really, really do), use your favourite web server's
15 WSGI interface rather than cherrypy's own one
16 '''
17
18 import cherrypy
19 import json
20
21 import config
22 import lib.magic
23
24 from contextlib import contextmanager
25
26 def expose_json(func):
27         '''decorate to set Content-Type on return to application/json and to expose to cherrypy'''
28         def wrapper(*argc,**argd):
29                 cherrypy.response.headers['Content-Type'] = 'application/json'
30                 return func(*argc,**argd)
31         wrapper.exposed = True
32         return wrapper
33
34 @contextmanager
35 def http_resource():
36         '''propogate KeyErrors and ValueErrors as HTTP errors'''
37         try:
38                 yield
39         except KeyError as err:
40                 raise cherrypy.HTTPError(404, str(err)) # Resource not found
41         except ValueError as err:
42                 raise cherrypy.HTTPError(400, str(err)) # Bad request
43
44 def simple_error_page(status, message, traceback, version):
45         return '{"error": "%s", "message": "%s"}' % (status, message)
46 def error_page(status, message, traceback, version):
47         '''simple error page for HTTP service rather than default overblown HTML one'''
48         return '{"error": "%s", "message": "%s",\n"traceback": "%s"}' % (status, message,traceback)
49
50 cherrypy.config.update({'error_page.400': simple_error_page,
51                         'error_page.404': simple_error_page,
52                         'error_page.405': simple_error_page,
53                         'error_page.default': error_page
54                         })
55
56 class Task(object):
57
58         @expose_json
59         def index(self):
60                 if cherrypy.request.method == 'GET':
61                         # List ALL THE TASKS!
62                         return json.dumps(self.magic.get_tasks(),indent=1)
63                 raise cherrypy.HTTPError(405)
64
65         @expose_json
66         def create(self):
67                 if cherrypy.request.method == 'POST':
68                         # TODO: ANY sort of taint checking
69                         with http_resource():
70                                 taskdata = json.load(cherrypy.request.body)
71                                 task = self.magic.create_task(taskdata)
72                         return json.dumps(task,indent=1)
73                 raise cherrypy.HTTPError(405)
74
75         @expose_json
76         def default(self, uuid, *args):
77                 # TODO: replace this horrible, horrible spagetti
78
79                 if len(args) == 0:
80                         if cherrypy.request.method == 'GET':
81                                 with http_resource(): # Show the task
82                                         return json.dumps(self.magic.get_task(uuid), indent=1)
83                         elif cherrypy.request.method == 'DELETE':
84                                 with http_resource(): # wipe task
85                                         self.magic.delete_task(uuid)
86                                         return '{}'
87                         else: raise cherrypy.HTTPError(405) # Invalid method
88
89                 if args[0] == 'available':
90                         # return items that we can do now
91                         if cherrypy.request.method == 'GET':
92                                 with http_resource():
93                                         return json.dumps(self.magic.ready_to_run(uuid), indent=1)
94                         else: raise cherrypy.HTTPError(405) # Invalid method
95                 elif args[0] == 'metadata':
96                         if cherrypy.request.method == 'GET':
97                                 with http_resource():
98                                         return json.dumps(self.magic.get_metadata(uuid), indent=1)
99                         elif cherrypy.request.method == 'POST':
100                                 with http_resource():
101                                         updatedata = json.load(cherrypy.request.body)
102                                         return json.dumps(self.magic.update_task_metadata(uuid,updatedata), indent=1)
103
104                 # Nothing so simple as a single task.
105                 args = dict(zip(['itemname','attrib'],args))
106
107                 if 'attrib' in args:
108                         # Handle attribute on the item (only state so far)
109                         if args['attrib'] == 'state':
110                                 if cherrypy.request.method == 'GET':
111                                         with http_resource():
112                                                 return self.magic.get_item(uuid,args['itemname'])['state']
113                         raise cherrypy.HTTPError(405)
114                 else:
115                         if cherrypy.request.method == 'GET':
116                                 with http_resource():
117                                         return json.dumps(self.magic.get_item(uuid,args['itemname']), indent=1)
118                         elif cherrypy.request.method == 'POST':
119                                 # Update stuff in the item
120                                 with http_resource():
121                                         updatedata = json.load(cherrypy.request.body)
122                                         return json.dumps(self.magic.update_item(uuid,args['itemname'],updatedata), indent=1)
123                 raise cherrypy.HTTPError(405)
124
125 class Root(object):
126         @cherrypy.expose
127         def index(self):
128                 cherrypy.response.headers['Content-Type'] = 'text/plain'
129                 return '''make magic httpd API is running and happy
130                 /task/          GET: list tasks
131                 /task/          POST: create new task  (takes { 'requirements': [] } at minimum)
132                 /task/uuid/     GET: show task
133 '''
134
135 def get_cherrypy_root(magiclib):
136         '''return the root object to be given to cherrypy
137
138         also defines which URLs work. Fun times :)
139         '''
140         root = Root()
141         root.task = Task()
142
143         # Allow the objects to access magic
144         # this may not be the optimal way to do it; might want to start more
145         # instances so everything isn't using the same Store connection
146         root.magic = magiclib
147         root.task.magic = magiclib
148
149         return root
150
151 def run_httpd():
152         magiclib = lib.magic.Magic() 
153         cpconfig = {'global': {'server.socket_host': config.httpd_listen_address, 'server.socket_port': config.httpd_listen_port}}
154         cherrypy.quickstart(get_cherrypy_root(magiclib), config=cpconfig)
155
156 if __name__ == '__main__':
157         run_httpd()