2014-08-09 21:12:55 +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.
|
|
|
|
*/
|
|
|
|
|
|
|
|
package apiserver
|
|
|
|
|
|
|
|
import (
|
|
|
|
"net/http"
|
|
|
|
"time"
|
|
|
|
|
2015-01-06 16:44:43 +00:00
|
|
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/admission"
|
2014-08-09 21:12:55 +00:00
|
|
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
2015-01-12 05:33:25 +00:00
|
|
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors"
|
2014-08-09 21:12:55 +00:00
|
|
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
|
2014-09-06 02:22:03 +00:00
|
|
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
|
2014-09-26 00:20:28 +00:00
|
|
|
|
2015-02-09 14:47:13 +00:00
|
|
|
"github.com/emicklei/go-restful"
|
2014-08-09 21:12:55 +00:00
|
|
|
)
|
|
|
|
|
2015-02-11 22:09:25 +00:00
|
|
|
// ContextFunc returns a Context given a request - a context must be returned
|
|
|
|
type ContextFunc func(req *restful.Request) api.Context
|
|
|
|
|
2015-02-09 14:47:13 +00:00
|
|
|
// ResourceNameFunc returns a name (and optional namespace) given a request - if no name is present
|
|
|
|
// an error must be returned.
|
|
|
|
type ResourceNameFunc func(req *restful.Request) (namespace, name string, err error)
|
2014-08-09 21:12:55 +00:00
|
|
|
|
2015-02-09 14:47:13 +00:00
|
|
|
// ObjectNameFunc returns the name (and optional namespace) of an object
|
|
|
|
type ObjectNameFunc func(obj runtime.Object) (namespace, name string, err error)
|
2015-02-10 01:04:37 +00:00
|
|
|
|
2015-02-09 14:47:13 +00:00
|
|
|
// ResourceNamespaceFunc returns the namespace associated with the given request - if no namespace
|
|
|
|
// is present an error must be returned.
|
|
|
|
type ResourceNamespaceFunc func(req *restful.Request) (namespace string, err error)
|
2015-02-10 01:04:37 +00:00
|
|
|
|
2015-02-09 14:47:13 +00:00
|
|
|
// LinkResourceFunc updates the provided object with a SelfLink that is appropriate for the current
|
|
|
|
// request.
|
|
|
|
type LinkResourceFunc func(req *restful.Request, obj runtime.Object) error
|
2014-08-09 21:12:55 +00:00
|
|
|
|
2015-02-09 14:47:13 +00:00
|
|
|
// GetResource returns a function that handles retrieving a single resource from a RESTStorage object.
|
2015-02-11 22:09:25 +00:00
|
|
|
func GetResource(r RESTGetter, ctxFn ContextFunc, nameFn ResourceNameFunc, linkFn LinkResourceFunc, codec runtime.Codec) restful.RouteFunction {
|
2015-02-09 14:47:13 +00:00
|
|
|
return func(req *restful.Request, res *restful.Response) {
|
|
|
|
w := res.ResponseWriter
|
|
|
|
namespace, name, err := nameFn(req)
|
|
|
|
if err != nil {
|
|
|
|
notFound(w, req.Request)
|
|
|
|
return
|
|
|
|
}
|
2015-02-11 22:09:25 +00:00
|
|
|
ctx := ctxFn(req)
|
2015-02-09 14:47:13 +00:00
|
|
|
if len(namespace) > 0 {
|
|
|
|
ctx = api.WithNamespace(ctx, namespace)
|
|
|
|
}
|
|
|
|
item, err := r.Get(ctx, name)
|
|
|
|
if err != nil {
|
|
|
|
errorJSON(err, codec, w)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if err := linkFn(req, item); err != nil {
|
|
|
|
errorJSON(err, codec, w)
|
|
|
|
return
|
2014-12-09 19:23:21 +00:00
|
|
|
}
|
2015-02-09 14:47:13 +00:00
|
|
|
writeJSON(http.StatusOK, codec, item, w)
|
2014-11-24 18:35:24 +00:00
|
|
|
}
|
2015-02-09 14:47:13 +00:00
|
|
|
}
|
2014-12-09 19:23:21 +00:00
|
|
|
|
2015-02-09 14:47:13 +00:00
|
|
|
// ListResource returns a function that handles retrieving a list of resources from a RESTStorage object.
|
2015-02-11 22:09:25 +00:00
|
|
|
func ListResource(r RESTLister, ctxFn ContextFunc, namespaceFn ResourceNamespaceFunc, linkFn LinkResourceFunc, codec runtime.Codec) restful.RouteFunction {
|
2015-02-09 14:47:13 +00:00
|
|
|
return func(req *restful.Request, res *restful.Response) {
|
|
|
|
w := res.ResponseWriter
|
2014-10-30 22:04:11 +00:00
|
|
|
|
2015-02-09 14:47:13 +00:00
|
|
|
namespace, err := namespaceFn(req)
|
|
|
|
if err != nil {
|
|
|
|
notFound(w, req.Request)
|
|
|
|
return
|
|
|
|
}
|
2015-02-11 22:09:25 +00:00
|
|
|
ctx := ctxFn(req)
|
2015-02-09 14:47:13 +00:00
|
|
|
if len(namespace) > 0 {
|
|
|
|
ctx = api.WithNamespace(ctx, namespace)
|
|
|
|
}
|
|
|
|
label, err := labels.ParseSelector(req.Request.URL.Query().Get("labels"))
|
|
|
|
if err != nil {
|
|
|
|
errorJSON(err, codec, w)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
field, err := labels.ParseSelector(req.Request.URL.Query().Get("fields"))
|
|
|
|
if err != nil {
|
|
|
|
errorJSON(err, codec, w)
|
|
|
|
return
|
2014-10-30 22:04:11 +00:00
|
|
|
}
|
2014-09-26 00:20:28 +00:00
|
|
|
|
2015-02-09 14:47:13 +00:00
|
|
|
item, err := r.List(ctx, label, field)
|
|
|
|
if err != nil {
|
|
|
|
errorJSON(err, codec, w)
|
|
|
|
return
|
2014-12-09 19:23:21 +00:00
|
|
|
}
|
2015-02-09 14:47:13 +00:00
|
|
|
if err := linkFn(req, item); err != nil {
|
|
|
|
errorJSON(err, codec, w)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
writeJSON(http.StatusOK, codec, item, w)
|
2014-11-24 18:35:24 +00:00
|
|
|
}
|
2014-09-26 00:20:28 +00:00
|
|
|
}
|
|
|
|
|
2015-02-09 14:47:13 +00:00
|
|
|
// CreateResource returns a function that will handle a resource creation.
|
2015-02-11 22:09:25 +00:00
|
|
|
func CreateResource(r RESTCreater, ctxFn ContextFunc, namespaceFn ResourceNamespaceFunc, linkFn LinkResourceFunc, codec runtime.Codec, resource string, admit admission.Interface) restful.RouteFunction {
|
2015-02-09 14:47:13 +00:00
|
|
|
return func(req *restful.Request, res *restful.Response) {
|
|
|
|
w := res.ResponseWriter
|
|
|
|
|
|
|
|
// TODO: we either want to remove timeout or document it (if we document, move timeout out of this function and declare it in api_installer)
|
|
|
|
timeout := parseTimeout(req.Request.URL.Query().Get("timeout"))
|
|
|
|
|
|
|
|
namespace, err := namespaceFn(req)
|
|
|
|
if err != nil {
|
|
|
|
notFound(w, req.Request)
|
|
|
|
return
|
|
|
|
}
|
2015-02-11 22:09:25 +00:00
|
|
|
ctx := ctxFn(req)
|
2015-02-09 14:47:13 +00:00
|
|
|
if len(namespace) > 0 {
|
|
|
|
ctx = api.WithNamespace(ctx, namespace)
|
2014-09-26 00:20:28 +00:00
|
|
|
}
|
|
|
|
|
2015-02-09 14:47:13 +00:00
|
|
|
body, err := readBody(req.Request)
|
|
|
|
if err != nil {
|
|
|
|
errorJSON(err, codec, w)
|
|
|
|
return
|
2014-08-09 21:12:55 +00:00
|
|
|
}
|
|
|
|
|
2015-02-09 14:47:13 +00:00
|
|
|
obj := r.New()
|
|
|
|
if err := codec.DecodeInto(body, obj); err != nil {
|
|
|
|
errorJSON(err, codec, w)
|
|
|
|
return
|
2014-08-09 21:12:55 +00:00
|
|
|
}
|
2015-02-09 14:47:13 +00:00
|
|
|
|
|
|
|
err = admit.Admit(admission.NewAttributesRecord(obj, namespace, resource, "CREATE"))
|
|
|
|
if err != nil {
|
|
|
|
errorJSON(err, codec, w)
|
|
|
|
return
|
2015-01-12 05:33:25 +00:00
|
|
|
}
|
|
|
|
|
2015-02-10 14:26:26 +00:00
|
|
|
result, err := finishRequest(timeout, func() (runtime.Object, error) {
|
2015-02-11 23:33:14 +00:00
|
|
|
out, err := r.Create(ctx, obj)
|
|
|
|
if status, ok := out.(*api.Status); ok && err == nil && status.Code == 0 {
|
|
|
|
status.Code = http.StatusCreated
|
|
|
|
}
|
|
|
|
return out, err
|
2015-02-10 14:26:26 +00:00
|
|
|
})
|
2014-08-09 21:12:55 +00:00
|
|
|
if err != nil {
|
2015-02-09 14:47:13 +00:00
|
|
|
errorJSON(err, codec, w)
|
|
|
|
return
|
2014-08-09 21:12:55 +00:00
|
|
|
}
|
2015-02-09 14:47:13 +00:00
|
|
|
|
2015-02-10 14:26:26 +00:00
|
|
|
if err := linkFn(req, result); err != nil {
|
2015-02-09 14:47:13 +00:00
|
|
|
errorJSON(err, codec, w)
|
|
|
|
return
|
2014-08-09 21:12:55 +00:00
|
|
|
}
|
2015-01-06 16:44:43 +00:00
|
|
|
|
2015-02-10 14:26:26 +00:00
|
|
|
writeJSON(http.StatusCreated, codec, result, w)
|
2015-02-09 14:47:13 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// UpdateResource returns a function that will handle a resource update
|
2015-02-11 22:09:25 +00:00
|
|
|
func UpdateResource(r RESTUpdater, ctxFn ContextFunc, nameFn ResourceNameFunc, objNameFunc ObjectNameFunc, linkFn LinkResourceFunc, codec runtime.Codec, resource string, admit admission.Interface) restful.RouteFunction {
|
2015-02-09 14:47:13 +00:00
|
|
|
return func(req *restful.Request, res *restful.Response) {
|
|
|
|
w := res.ResponseWriter
|
|
|
|
|
|
|
|
// TODO: we either want to remove timeout or document it (if we document, move timeout out of this function and declare it in api_installer)
|
|
|
|
timeout := parseTimeout(req.Request.URL.Query().Get("timeout"))
|
|
|
|
|
|
|
|
namespace, name, err := nameFn(req)
|
2015-01-06 16:44:43 +00:00
|
|
|
if err != nil {
|
2015-02-09 14:47:13 +00:00
|
|
|
notFound(w, req.Request)
|
|
|
|
return
|
|
|
|
}
|
2015-02-11 22:09:25 +00:00
|
|
|
ctx := ctxFn(req)
|
2015-02-09 14:47:13 +00:00
|
|
|
if len(namespace) > 0 {
|
|
|
|
ctx = api.WithNamespace(ctx, namespace)
|
2015-01-06 16:44:43 +00:00
|
|
|
}
|
|
|
|
|
2015-02-09 14:47:13 +00:00
|
|
|
body, err := readBody(req.Request)
|
2014-08-09 21:12:55 +00:00
|
|
|
if err != nil {
|
2015-02-09 14:47:13 +00:00
|
|
|
errorJSON(err, codec, w)
|
|
|
|
return
|
2014-08-09 21:12:55 +00:00
|
|
|
}
|
|
|
|
|
2015-02-09 14:47:13 +00:00
|
|
|
obj := r.New()
|
|
|
|
if err := codec.DecodeInto(body, obj); err != nil {
|
|
|
|
errorJSON(err, codec, w)
|
|
|
|
return
|
2014-08-09 21:12:55 +00:00
|
|
|
}
|
2015-02-09 14:47:13 +00:00
|
|
|
|
|
|
|
objNamespace, objName, err := objNameFunc(obj)
|
|
|
|
if err != nil {
|
|
|
|
errorJSON(err, codec, w)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if objName != name {
|
|
|
|
errorJSON(errors.NewBadRequest("the name of the object does not match the name on the URL"), codec, w)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if len(namespace) > 0 {
|
|
|
|
if len(objNamespace) > 0 && objNamespace != namespace {
|
|
|
|
errorJSON(errors.NewBadRequest("the namespace of the object does not match the namespace on the request"), codec, w)
|
|
|
|
return
|
|
|
|
}
|
2015-01-12 05:33:25 +00:00
|
|
|
}
|
2015-01-06 16:44:43 +00:00
|
|
|
|
2015-02-09 14:47:13 +00:00
|
|
|
err = admit.Admit(admission.NewAttributesRecord(obj, namespace, resource, "UPDATE"))
|
2015-01-06 16:44:43 +00:00
|
|
|
if err != nil {
|
2015-02-09 14:47:13 +00:00
|
|
|
errorJSON(err, codec, w)
|
|
|
|
return
|
2015-01-06 16:44:43 +00:00
|
|
|
}
|
|
|
|
|
2015-02-10 14:26:26 +00:00
|
|
|
wasCreated := false
|
|
|
|
result, err := finishRequest(timeout, func() (runtime.Object, error) {
|
|
|
|
obj, created, err := r.Update(ctx, obj)
|
|
|
|
wasCreated = created
|
|
|
|
return obj, err
|
|
|
|
})
|
2015-02-09 14:47:13 +00:00
|
|
|
if err != nil {
|
|
|
|
errorJSON(err, codec, w)
|
|
|
|
return
|
2014-08-09 21:12:55 +00:00
|
|
|
}
|
2015-02-09 14:47:13 +00:00
|
|
|
|
2015-02-10 14:26:26 +00:00
|
|
|
if err := linkFn(req, result); err != nil {
|
2015-02-09 14:47:13 +00:00
|
|
|
errorJSON(err, codec, w)
|
|
|
|
return
|
2015-01-12 05:33:25 +00:00
|
|
|
}
|
|
|
|
|
2015-02-09 14:47:13 +00:00
|
|
|
status := http.StatusOK
|
2015-02-10 14:26:26 +00:00
|
|
|
if wasCreated {
|
2015-02-09 14:47:13 +00:00
|
|
|
status = http.StatusCreated
|
|
|
|
}
|
2015-02-10 14:26:26 +00:00
|
|
|
writeJSON(status, codec, result, w)
|
2015-02-09 14:47:13 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// DeleteResource returns a function that will handle a resource deletion
|
2015-02-11 22:09:25 +00:00
|
|
|
func DeleteResource(r RESTDeleter, ctxFn ContextFunc, nameFn ResourceNameFunc, linkFn LinkResourceFunc, codec runtime.Codec, resource, kind string, admit admission.Interface) restful.RouteFunction {
|
2015-02-09 14:47:13 +00:00
|
|
|
return func(req *restful.Request, res *restful.Response) {
|
|
|
|
w := res.ResponseWriter
|
|
|
|
|
|
|
|
// TODO: we either want to remove timeout or document it (if we document, move timeout out of this function and declare it in api_installer)
|
|
|
|
timeout := parseTimeout(req.Request.URL.Query().Get("timeout"))
|
|
|
|
|
|
|
|
namespace, name, err := nameFn(req)
|
2014-08-09 21:12:55 +00:00
|
|
|
if err != nil {
|
2015-02-09 14:47:13 +00:00
|
|
|
notFound(w, req.Request)
|
|
|
|
return
|
2014-08-09 21:12:55 +00:00
|
|
|
}
|
2015-02-11 22:09:25 +00:00
|
|
|
ctx := ctxFn(req)
|
2015-02-09 14:47:13 +00:00
|
|
|
if len(namespace) > 0 {
|
|
|
|
ctx = api.WithNamespace(ctx, namespace)
|
|
|
|
}
|
|
|
|
|
|
|
|
err = admit.Admit(admission.NewAttributesRecord(nil, namespace, resource, "DELETE"))
|
2014-08-09 21:12:55 +00:00
|
|
|
if err != nil {
|
2015-02-09 14:47:13 +00:00
|
|
|
errorJSON(err, codec, w)
|
|
|
|
return
|
2014-08-09 21:12:55 +00:00
|
|
|
}
|
2015-01-06 16:44:43 +00:00
|
|
|
|
2015-02-10 14:26:26 +00:00
|
|
|
result, err := finishRequest(timeout, func() (runtime.Object, error) {
|
|
|
|
return r.Delete(ctx, name)
|
|
|
|
})
|
2014-08-09 21:12:55 +00:00
|
|
|
if err != nil {
|
2015-02-09 14:47:13 +00:00
|
|
|
errorJSON(err, codec, w)
|
|
|
|
return
|
2014-08-09 21:12:55 +00:00
|
|
|
}
|
|
|
|
|
2015-02-09 14:47:13 +00:00
|
|
|
// if the RESTDeleter returns a nil object, fill out a status. Callers may return a valid
|
|
|
|
// object with the response.
|
2015-02-10 14:26:26 +00:00
|
|
|
if result == nil {
|
|
|
|
result = &api.Status{
|
2015-02-09 14:47:13 +00:00
|
|
|
Status: api.StatusSuccess,
|
|
|
|
Code: http.StatusOK,
|
|
|
|
Details: &api.StatusDetails{
|
|
|
|
ID: name,
|
|
|
|
Kind: kind,
|
|
|
|
},
|
|
|
|
}
|
2015-02-10 14:26:26 +00:00
|
|
|
} else {
|
|
|
|
// when a non-status response is returned, set the self link
|
|
|
|
if _, ok := result.(*api.Status); !ok {
|
|
|
|
if err := linkFn(req, result); err != nil {
|
|
|
|
errorJSON(err, codec, w)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
2015-02-09 14:47:13 +00:00
|
|
|
}
|
2015-02-10 14:26:26 +00:00
|
|
|
writeJSON(http.StatusOK, codec, result, w)
|
2014-08-09 21:12:55 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-02-10 14:26:26 +00:00
|
|
|
// resultFunc is a function that returns a rest result and can be run in a goroutine
|
|
|
|
type resultFunc func() (runtime.Object, error)
|
|
|
|
|
|
|
|
// finishRequest makes a given resultFunc asynchronous and handles errors returned by the response.
|
2015-02-09 14:47:13 +00:00
|
|
|
// Any api.Status object returned is considered an "error", which interrupts the normal response flow.
|
2015-02-10 14:26:26 +00:00
|
|
|
func finishRequest(timeout time.Duration, fn resultFunc) (result runtime.Object, err error) {
|
|
|
|
ch := make(chan runtime.Object)
|
|
|
|
errCh := make(chan error)
|
|
|
|
go func() {
|
|
|
|
if result, err := fn(); err != nil {
|
|
|
|
errCh <- err
|
|
|
|
} else {
|
|
|
|
ch <- result
|
2015-02-09 14:47:13 +00:00
|
|
|
}
|
2015-02-10 14:26:26 +00:00
|
|
|
}()
|
|
|
|
|
|
|
|
select {
|
|
|
|
case result = <-ch:
|
|
|
|
if status, ok := result.(*api.Status); ok {
|
2015-02-09 14:47:13 +00:00
|
|
|
return nil, errors.FromObject(status)
|
|
|
|
}
|
2015-02-10 14:26:26 +00:00
|
|
|
return result, nil
|
|
|
|
case err = <-errCh:
|
|
|
|
return nil, err
|
2015-02-09 14:47:13 +00:00
|
|
|
case <-time.After(timeout):
|
|
|
|
return nil, errors.NewTimeoutError("request did not complete within allowed duration")
|
|
|
|
}
|
2014-08-09 21:12:55 +00:00
|
|
|
}
|
|
|
|
|
2015-02-09 14:47:13 +00:00
|
|
|
type linkFunc func(namespace, name string) (path string, query string)
|
|
|
|
|
|
|
|
// setSelfLink sets the self link of an object (or the child items in a list) to the base URL of the request
|
|
|
|
// plus the path and query generated by the provided linkFunc
|
|
|
|
func setSelfLink(obj runtime.Object, req *http.Request, linker runtime.SelfLinker, fn linkFunc) error {
|
|
|
|
namespace, err := linker.Namespace(obj)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
name, err := linker.Name(obj)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
path, query := fn(namespace, name)
|
|
|
|
|
|
|
|
newURL := *req.URL
|
|
|
|
newURL.Path = path
|
|
|
|
newURL.RawQuery = query
|
|
|
|
newURL.Fragment = ""
|
|
|
|
|
|
|
|
if err := linker.SetSelfLink(obj, newURL.String()); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if !runtime.IsListType(obj) {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Set self-link of objects in the list.
|
|
|
|
items, err := runtime.ExtractList(obj)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
for i := range items {
|
|
|
|
if err := setSelfLink(items[i], req, linker, fn); err != nil {
|
|
|
|
return err
|
2014-08-09 21:12:55 +00:00
|
|
|
}
|
|
|
|
}
|
2015-02-09 14:47:13 +00:00
|
|
|
return runtime.SetList(obj, items)
|
2014-08-09 21:12:55 +00:00
|
|
|
}
|