2
// gosdc - Go library to interact with the Joyent CloudAPI
4
// CloudAPI double testing service - HTTP API implementation
6
// Copyright (c) 2013 Joyent Inc.
8
// Written by Daniele Stroppa <daniele.stroppa@joyent.com>
21
"github.com/joyent/gosdc/cloudapi"
24
// ErrorResponse defines a single HTTP error response.
25
type ErrorResponse struct {
30
headers map[string]string
35
ErrNotAllowed = &ErrorResponse{
36
http.StatusMethodNotAllowed,
37
"Method is not allowed",
38
"text/plain; charset=UTF-8",
39
"MethodNotAllowedError",
43
ErrNotFound = &ErrorResponse{
46
"text/plain; charset=UTF-8",
51
ErrBadRequest = &ErrorResponse{
52
http.StatusBadRequest,
53
"Malformed request url",
54
"text/plain; charset=UTF-8",
61
func (e *ErrorResponse) Error() string {
65
func (e *ErrorResponse) ServeHTTP(w http.ResponseWriter, r *http.Request) {
66
if e.contentType != "" {
67
w.Header().Set("Content-Type", e.contentType)
71
for h, v := range e.headers {
75
// workaround for https://code.google.com/p/go/issues/detail?id=4454
76
w.Header().Set("Content-Length", strconv.Itoa(len(body)))
85
type cloudapiHandler struct {
87
method func(m *CloudAPI, w http.ResponseWriter, r *http.Request) error
90
func (h *cloudapiHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
92
// handle trailing slash in the path
93
if strings.HasSuffix(path, "/") && path != "/" {
94
ErrNotFound.ServeHTTP(w, r)
97
err := h.method(h.cloudapi, w, r)
101
var resp http.Handler
102
resp, _ = err.(http.Handler)
104
resp = &ErrorResponse{
105
http.StatusInternalServerError,
106
`{"internalServerError":{"message":"Unkown Error",code:500}}`,
116
func writeResponse(w http.ResponseWriter, code int, body []byte) {
117
// workaround for https://code.google.com/p/go/issues/detail?id=4454
118
w.Header().Set("Content-Length", strconv.Itoa(len(body)))
123
// sendJSON sends the specified response serialized as JSON.
124
func sendJSON(code int, resp interface{}, w http.ResponseWriter, r *http.Request) error {
125
data, err := json.Marshal(resp)
129
writeResponse(w, code, data)
133
func processFilter(rawQuery string) map[string]string {
134
var filters map[string]string
136
filters = make(map[string]string)
137
for _, filter := range strings.Split(rawQuery, "&") {
138
filters[filter[:strings.Index(filter, "=")]] = filter[strings.Index(filter, "=")+1:]
145
func (cloudapi *CloudAPI) handler(method func(m *CloudAPI, w http.ResponseWriter, r *http.Request) error) http.Handler {
146
return &cloudapiHandler{cloudapi, method}
149
// handleKeys handles the keys HTTP API.
150
func (c *CloudAPI) handleKeys(w http.ResponseWriter, r *http.Request) error {
151
prefix := fmt.Sprintf("/%s/keys/", c.ServiceInstance.UserAccount)
152
keyName := strings.TrimPrefix(r.URL.Path, prefix)
155
if strings.HasSuffix(r.URL.Path, "keys") {
157
keys, err := c.ListKeys()
162
keys = []cloudapi.Key{}
165
return sendJSON(http.StatusOK, resp, w, r)
168
key, err := c.GetKey(keyName)
173
key = &cloudapi.Key{}
176
return sendJSON(http.StatusOK, resp, w, r)
179
if strings.HasSuffix(r.URL.Path, "keys") {
185
opts := &cloudapi.CreateKeyOpts{}
186
body, errB := ioutil.ReadAll(r.Body)
191
if errJ := json.Unmarshal(body, opts); errJ != nil {
197
k, err := c.CreateKey(name, key)
205
return sendJSON(http.StatusCreated, resp, w, r)
212
if strings.HasSuffix(r.URL.Path, "keys") {
216
err := c.DeleteKey(keyName)
220
return sendJSON(http.StatusNoContent, nil, w, r)
223
return fmt.Errorf("unknown request method %q for %s", r.Method, r.URL.Path)
226
// handleImages handles the images HTTP API.
227
func (c *CloudAPI) handleImages(w http.ResponseWriter, r *http.Request) error {
228
prefix := fmt.Sprintf("/%s/images/", c.ServiceInstance.UserAccount)
229
imageId := strings.TrimPrefix(r.URL.Path, prefix)
232
if strings.HasSuffix(r.URL.Path, "images") {
234
images, err := c.ListImages(processFilter(r.URL.RawQuery))
239
images = []cloudapi.Image{}
242
return sendJSON(http.StatusOK, resp, w, r)
245
image, err := c.GetImage(imageId)
250
image = &cloudapi.Image{}
253
return sendJSON(http.StatusOK, resp, w, r)
256
if strings.HasSuffix(r.URL.Path, "images") {
257
// CreateImageFromMachine
265
/*if strings.HasSuffix(r.URL.Path, "images") {
268
err := c.DeleteImage(imageId)
272
return sendJSON(http.StatusNoContent, nil, w, r)
276
return fmt.Errorf("unknown request method %q for %s", r.Method, r.URL.Path)
279
// handlePackages handles the packages HTTP API.
280
func (c *CloudAPI) handlePackages(w http.ResponseWriter, r *http.Request) error {
281
prefix := fmt.Sprintf("/%s/packages/", c.ServiceInstance.UserAccount)
282
pkgName := strings.TrimPrefix(r.URL.Path, prefix)
285
if strings.HasSuffix(r.URL.Path, "packages") {
287
pkgs, err := c.ListPackages(processFilter(r.URL.RawQuery))
292
pkgs = []cloudapi.Package{}
295
return sendJSON(http.StatusOK, resp, w, r)
298
pkg, err := c.GetPackage(pkgName)
303
pkg = &cloudapi.Package{}
306
return sendJSON(http.StatusOK, resp, w, r)
315
return fmt.Errorf("unknown request method %q for %s", r.Method, r.URL.Path)
318
// handleMachines handles the machine HTTP API.
319
func (c *CloudAPI) handleMachines(w http.ResponseWriter, r *http.Request) error {
320
prefix := fmt.Sprintf("/%s/machines/", c.ServiceInstance.UserAccount)
321
machineId := strings.TrimPrefix(r.URL.Path, prefix)
324
if strings.HasSuffix(r.URL.Path, "machines") {
326
machines, err := c.ListMachines(processFilter(r.URL.RawQuery))
331
machines = []*cloudapi.Machine{}
334
return sendJSON(http.StatusOK, resp, w, r)
335
} else if strings.HasSuffix(r.URL.Path, "fwrules") {
336
// ListMachineFirewallRules
337
machineId = strings.TrimSuffix(machineId, "/fwrules")
338
fwRules, err := c.ListMachineFirewallRules(machineId)
343
fwRules = []*cloudapi.FirewallRule{}
346
return sendJSON(http.StatusOK, resp, w, r)
349
machine, err := c.GetMachine(machineId)
354
machine = &cloudapi.Machine{}
357
return sendJSON(http.StatusOK, resp, w, r)
360
if strings.HasSuffix(r.URL.Path, "machines") {
362
count, err := c.CountMachines()
367
return sendJSON(http.StatusOK, resp, w, r)
372
if strings.HasSuffix(r.URL.Path, "machines") {
378
metadata map[string]string
379
tags map[string]string
381
opts := &cloudapi.CreateMachineOpts{}
382
body, errB := ioutil.ReadAll(r.Body)
387
if errJ := json.Unmarshal(body, opts); errJ != nil {
393
metadata = opts.Metadata
396
machine, err := c.CreateMachine(name, pkg, image, metadata, tags)
401
machine = &cloudapi.Machine{}
404
return sendJSON(http.StatusCreated, resp, w, r)
405
} else if r.URL.Query().Get("action") == "stop" {
407
err := c.StopMachine(machineId)
411
return sendJSON(http.StatusAccepted, nil, w, r)
412
} else if r.URL.Query().Get("action") == "start" {
414
err := c.StartMachine(machineId)
418
return sendJSON(http.StatusAccepted, nil, w, r)
419
} else if r.URL.Query().Get("action") == "reboot" {
421
err := c.RebootMachine(machineId)
425
return sendJSON(http.StatusAccepted, nil, w, r)
426
} else if r.URL.Query().Get("action") == "resize" {
428
err := c.ResizeMachine(machineId, r.URL.Query().Get("package"))
432
return sendJSON(http.StatusAccepted, nil, w, r)
433
} else if r.URL.Query().Get("action") == "rename" {
435
err := c.RenameMachine(machineId, r.URL.Query().Get("name"))
439
return sendJSON(http.StatusAccepted, nil, w, r)
440
} else if r.URL.Query().Get("action") == "enable_firewall" {
441
//EnableFirewallMachine
442
err := c.EnableFirewallMachine(machineId)
446
return sendJSON(http.StatusAccepted, nil, w, r)
447
} else if r.URL.Query().Get("action") == "disable_firewall" {
448
//DisableFirewallMachine
449
err := c.DisableFirewallMachine(machineId)
453
return sendJSON(http.StatusAccepted, nil, w, r)
460
if strings.HasSuffix(r.URL.Path, "machines") {
464
err := c.DeleteMachine(machineId)
468
return sendJSON(http.StatusNoContent, nil, w, r)
471
return fmt.Errorf("unknown request method %q for %s", r.Method, r.URL.Path)
474
// handleFwRules handles the firewall rules HTTP API.
475
func (c *CloudAPI) handleFwRules(w http.ResponseWriter, r *http.Request) error {
476
prefix := fmt.Sprintf("/%s/fwrules/", c.ServiceInstance.UserAccount)
477
fwRuleId := strings.TrimPrefix(r.URL.Path, prefix)
480
if strings.HasSuffix(r.URL.Path, "fwrules") {
482
fwRules, err := c.ListFirewallRules()
487
fwRules = []*cloudapi.FirewallRule{}
490
return sendJSON(http.StatusOK, resp, w, r)
493
fwRule, err := c.GetFirewallRule(fwRuleId)
498
fwRule = &cloudapi.FirewallRule{}
501
return sendJSON(http.StatusOK, resp, w, r)
504
if strings.HasSuffix(r.URL.Path, "fwrules") {
505
// CreateFirewallRule
510
opts := &cloudapi.CreateFwRuleOpts{}
511
body, errB := ioutil.ReadAll(r.Body)
516
if errJ := json.Unmarshal(body, opts); errJ != nil {
520
enabled = opts.Enabled
522
fwRule, err := c.CreateFirewallRule(rule, enabled)
527
fwRule = &cloudapi.FirewallRule{}
530
return sendJSON(http.StatusCreated, resp, w, r)
531
} else if strings.HasSuffix(r.URL.Path, "enable") {
532
// EnableFirewallRule
533
fwRuleId = strings.TrimSuffix(fwRuleId, "/enable")
534
fwRule, err := c.EnableFirewallRule(fwRuleId)
539
fwRule = &cloudapi.FirewallRule{}
542
return sendJSON(http.StatusOK, resp, w, r)
543
} else if strings.HasSuffix(r.URL.Path, "disable") {
544
// DisableFirewallRule
545
fwRuleId = strings.TrimSuffix(fwRuleId, "/disable")
546
fwRule, err := c.DisableFirewallRule(fwRuleId)
551
fwRule = &cloudapi.FirewallRule{}
554
return sendJSON(http.StatusOK, resp, w, r)
556
// UpdateFirewallRule
561
opts := &cloudapi.CreateFwRuleOpts{}
562
body, errB := ioutil.ReadAll(r.Body)
567
if errJ := json.Unmarshal(body, opts); errJ != nil {
571
enabled = opts.Enabled
573
fwRule, err := c.UpdateFirewallRule(fwRuleId, rule, enabled)
578
fwRule = &cloudapi.FirewallRule{}
581
return sendJSON(http.StatusOK, resp, w, r)
586
if strings.HasSuffix(r.URL.Path, "fwrules") {
589
// DeleteFirewallRule
590
err := c.DeleteFirewallRule(fwRuleId)
594
return sendJSON(http.StatusNoContent, nil, w, r)
597
return fmt.Errorf("unknown request method %q for %s", r.Method, r.URL.Path)
600
// handleNetworks handles the networks HTTP API.
601
func (c *CloudAPI) handleNetworks(w http.ResponseWriter, r *http.Request) error {
602
prefix := fmt.Sprintf("/%s/networks/", c.ServiceInstance.UserAccount)
603
networkId := strings.TrimPrefix(r.URL.Path, prefix)
606
if strings.HasSuffix(r.URL.Path, "networks") {
608
networks, err := c.ListNetworks()
613
networks = []cloudapi.Network{}
616
return sendJSON(http.StatusOK, resp, w, r)
619
network, err := c.GetNetwork(networkId)
624
network = &cloudapi.Network{}
627
return sendJSON(http.StatusOK, resp, w, r)
636
return fmt.Errorf("unknown request method %q for %s", r.Method, r.URL.Path)
639
// setupHTTP attaches all the needed handlers to provide the HTTP API.
640
func (c *CloudAPI) SetupHTTP(mux *http.ServeMux) {
641
handlers := map[string]http.Handler{
643
"/$user/": ErrBadRequest,
644
"/$user/keys": c.handler((*CloudAPI).handleKeys),
645
"/$user/images": c.handler((*CloudAPI).handleImages),
646
"/$user/packages": c.handler((*CloudAPI).handlePackages),
647
"/$user/machines": c.handler((*CloudAPI).handleMachines),
648
//"/$user/datacenters": c.handler((*CloudAPI).handleDatacenters),
649
"/$user/fwrules": c.handler((*CloudAPI).handleFwRules),
650
"/$user/networks": c.handler((*CloudAPI).handleNetworks),
652
for path, h := range handlers {
653
path = strings.Replace(path, "$user", c.ServiceInstance.UserAccount, 1)
654
if !strings.HasSuffix(path, "/") {
655
mux.Handle(path+"/", h)