2014-06-06 23:40:48 +00:00
|
|
|
/*
|
|
|
|
Copyright 2014 Google Inc. All rights reserved.
|
|
|
|
|
|
|
|
Licensed 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.
|
|
|
|
*/
|
2014-06-16 05:34:16 +00:00
|
|
|
|
2014-06-06 23:40:48 +00:00
|
|
|
package apiserver
|
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
|
|
|
"io/ioutil"
|
|
|
|
"log"
|
|
|
|
"net/http"
|
|
|
|
"net/url"
|
2014-06-17 21:59:37 +00:00
|
|
|
"runtime/debug"
|
2014-06-06 23:40:48 +00:00
|
|
|
"strings"
|
2014-06-17 01:03:44 +00:00
|
|
|
|
|
|
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
|
2014-06-06 23:40:48 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
// RESTStorage is a generic interface for RESTful storage services
|
|
|
|
type RESTStorage interface {
|
2014-06-17 01:03:44 +00:00
|
|
|
List(labels.Query) (interface{}, error)
|
2014-06-06 23:40:48 +00:00
|
|
|
Get(id string) (interface{}, error)
|
|
|
|
Delete(id string) error
|
|
|
|
Extract(body string) (interface{}, error)
|
|
|
|
Create(interface{}) error
|
|
|
|
Update(interface{}) error
|
|
|
|
}
|
|
|
|
|
|
|
|
// Status is a return value for calls that don't return other objects
|
|
|
|
type Status struct {
|
2014-06-16 20:21:53 +00:00
|
|
|
Success bool
|
2014-06-06 23:40:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// ApiServer is an HTTPHandler that delegates to RESTStorage objects.
|
|
|
|
// It handles URLs of the form:
|
|
|
|
// ${prefix}/${storage_key}[/${object_name}]
|
|
|
|
// Where 'prefix' is an arbitrary string, and 'storage_key' points to a RESTStorage object stored in storage.
|
|
|
|
//
|
|
|
|
// TODO: consider migrating this to go-restful which is a more full-featured version of the same thing.
|
|
|
|
type ApiServer struct {
|
|
|
|
prefix string
|
|
|
|
storage map[string]RESTStorage
|
|
|
|
}
|
|
|
|
|
|
|
|
// New creates a new ApiServer object.
|
|
|
|
// 'storage' contains a map of handlers.
|
|
|
|
// 'prefix' is the hosting path prefix.
|
2014-06-08 04:56:14 +00:00
|
|
|
func New(storage map[string]RESTStorage, prefix string) *ApiServer {
|
2014-06-06 23:40:48 +00:00
|
|
|
return &ApiServer{
|
|
|
|
storage: storage,
|
|
|
|
prefix: prefix,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (server *ApiServer) handleIndex(w http.ResponseWriter) {
|
|
|
|
w.WriteHeader(http.StatusOK)
|
|
|
|
// TODO: serve this out of a file?
|
|
|
|
data := "<html><body>Welcome to Kubernetes</body></html>"
|
|
|
|
fmt.Fprint(w, data)
|
|
|
|
}
|
|
|
|
|
|
|
|
// HTTP Handler interface
|
|
|
|
func (server *ApiServer) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
2014-06-13 21:58:08 +00:00
|
|
|
defer func() {
|
|
|
|
if x := recover(); x != nil {
|
|
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
|
|
fmt.Fprint(w, "apiserver panic. Look in log for details.")
|
2014-06-17 21:59:37 +00:00
|
|
|
log.Printf("ApiServer panic'd: %#v\n%s\n", x, debug.Stack())
|
2014-06-13 21:58:08 +00:00
|
|
|
}
|
|
|
|
}()
|
2014-06-06 23:40:48 +00:00
|
|
|
log.Printf("%s %s", req.Method, req.RequestURI)
|
|
|
|
url, err := url.ParseRequestURI(req.RequestURI)
|
|
|
|
if err != nil {
|
|
|
|
server.error(err, w)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if url.Path == "/index.html" || url.Path == "/" || url.Path == "" {
|
|
|
|
server.handleIndex(w)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if !strings.HasPrefix(url.Path, server.prefix) {
|
|
|
|
server.notFound(req, w)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
requestParts := strings.Split(url.Path[len(server.prefix):], "/")[1:]
|
|
|
|
if len(requestParts) < 1 {
|
|
|
|
server.notFound(req, w)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
storage := server.storage[requestParts[0]]
|
|
|
|
if storage == nil {
|
|
|
|
server.notFound(req, w)
|
|
|
|
return
|
|
|
|
} else {
|
|
|
|
server.handleREST(requestParts, url, req, w, storage)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (server *ApiServer) notFound(req *http.Request, w http.ResponseWriter) {
|
|
|
|
w.WriteHeader(404)
|
|
|
|
fmt.Fprintf(w, "Not Found: %#v", req)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (server *ApiServer) write(statusCode int, object interface{}, w http.ResponseWriter) {
|
|
|
|
w.WriteHeader(statusCode)
|
|
|
|
output, err := json.MarshalIndent(object, "", " ")
|
|
|
|
if err != nil {
|
|
|
|
server.error(err, w)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
w.Write(output)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (server *ApiServer) error(err error, w http.ResponseWriter) {
|
|
|
|
w.WriteHeader(500)
|
|
|
|
fmt.Fprintf(w, "Internal Error: %#v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (server *ApiServer) readBody(req *http.Request) (string, error) {
|
|
|
|
defer req.Body.Close()
|
|
|
|
body, err := ioutil.ReadAll(req.Body)
|
|
|
|
return string(body), err
|
|
|
|
}
|
|
|
|
|
2014-06-16 05:34:16 +00:00
|
|
|
// handleREST is the main dispatcher for the server. It switches on the HTTP method, and then
|
|
|
|
// on path length, according to the following table:
|
|
|
|
// Method Path Action
|
|
|
|
// GET /foo list
|
|
|
|
// GET /foo/bar get 'bar'
|
|
|
|
// POST /foo create
|
|
|
|
// PUT /foo/bar update 'bar'
|
|
|
|
// DELETE /foo/bar delete 'bar'
|
|
|
|
// Returns 404 if the method/pattern doesn't match one of these entries
|
2014-06-17 00:49:50 +00:00
|
|
|
func (server *ApiServer) handleREST(parts []string, requestUrl *url.URL, req *http.Request, w http.ResponseWriter, storage RESTStorage) {
|
2014-06-06 23:40:48 +00:00
|
|
|
switch req.Method {
|
|
|
|
case "GET":
|
|
|
|
switch len(parts) {
|
|
|
|
case 1:
|
2014-06-17 01:03:44 +00:00
|
|
|
query, err := labels.ParseQuery(requestUrl.Query().Get("labels"))
|
2014-06-17 00:49:50 +00:00
|
|
|
if err != nil {
|
|
|
|
server.error(err, w)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
controllers, err := storage.List(query)
|
2014-06-06 23:40:48 +00:00
|
|
|
if err != nil {
|
|
|
|
server.error(err, w)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
server.write(200, controllers, w)
|
|
|
|
case 2:
|
2014-06-17 00:49:50 +00:00
|
|
|
item, err := storage.Get(parts[1])
|
2014-06-06 23:40:48 +00:00
|
|
|
if err != nil {
|
|
|
|
server.error(err, w)
|
|
|
|
return
|
|
|
|
}
|
2014-06-17 00:49:50 +00:00
|
|
|
if item == nil {
|
2014-06-06 23:40:48 +00:00
|
|
|
server.notFound(req, w)
|
|
|
|
return
|
|
|
|
}
|
2014-06-17 00:49:50 +00:00
|
|
|
server.write(200, item, w)
|
2014-06-06 23:40:48 +00:00
|
|
|
default:
|
|
|
|
server.notFound(req, w)
|
|
|
|
}
|
|
|
|
return
|
|
|
|
case "POST":
|
|
|
|
if len(parts) != 1 {
|
|
|
|
server.notFound(req, w)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
body, err := server.readBody(req)
|
|
|
|
if err != nil {
|
|
|
|
server.error(err, w)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
obj, err := storage.Extract(body)
|
|
|
|
if err != nil {
|
|
|
|
server.error(err, w)
|
|
|
|
return
|
|
|
|
}
|
2014-06-17 17:50:42 +00:00
|
|
|
err = storage.Create(obj)
|
|
|
|
if err != nil {
|
|
|
|
server.error(err, w)
|
|
|
|
} else {
|
|
|
|
server.write(200, obj, w)
|
|
|
|
}
|
2014-06-06 23:40:48 +00:00
|
|
|
return
|
|
|
|
case "DELETE":
|
|
|
|
if len(parts) != 2 {
|
|
|
|
server.notFound(req, w)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
err := storage.Delete(parts[1])
|
|
|
|
if err != nil {
|
|
|
|
server.error(err, w)
|
|
|
|
return
|
|
|
|
}
|
2014-06-16 20:21:53 +00:00
|
|
|
server.write(200, Status{Success: true}, w)
|
2014-06-06 23:40:48 +00:00
|
|
|
return
|
|
|
|
case "PUT":
|
|
|
|
if len(parts) != 2 {
|
|
|
|
server.notFound(req, w)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
body, err := server.readBody(req)
|
|
|
|
if err != nil {
|
|
|
|
server.error(err, w)
|
|
|
|
}
|
|
|
|
obj, err := storage.Extract(body)
|
|
|
|
if err != nil {
|
|
|
|
server.error(err, w)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
err = storage.Update(obj)
|
|
|
|
if err != nil {
|
|
|
|
server.error(err, w)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
server.write(200, obj, w)
|
|
|
|
return
|
|
|
|
default:
|
|
|
|
server.notFound(req, w)
|
|
|
|
}
|
|
|
|
}
|