2014-08-09 21:12:55 +00:00
/ *
2015-05-01 16:19:44 +00:00
Copyright 2014 The Kubernetes Authors All rights reserved .
2014-08-09 21:12:55 +00:00
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 (
2016-05-09 15:54:43 +00:00
"encoding/hex"
2015-09-29 18:37:26 +00:00
"encoding/json"
2015-03-04 20:57:05 +00:00
"fmt"
2015-10-16 13:07:14 +00:00
"math/rand"
2014-08-09 21:12:55 +00:00
"net/http"
2015-03-02 23:00:09 +00:00
"net/url"
2015-02-12 19:21:47 +00:00
gpath "path"
2015-09-30 21:03:27 +00:00
"strings"
2014-08-09 21:12:55 +00:00
"time"
2015-08-05 22:03:47 +00:00
"k8s.io/kubernetes/pkg/admission"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/errors"
2015-11-12 10:45:42 +00:00
"k8s.io/kubernetes/pkg/api/meta"
2015-08-05 22:03:47 +00:00
"k8s.io/kubernetes/pkg/api/rest"
2015-09-09 21:59:11 +00:00
"k8s.io/kubernetes/pkg/api/unversioned"
2015-08-05 22:03:47 +00:00
"k8s.io/kubernetes/pkg/fields"
"k8s.io/kubernetes/pkg/runtime"
2015-08-28 18:01:31 +00:00
"k8s.io/kubernetes/pkg/util"
2016-01-15 07:32:10 +00:00
utilruntime "k8s.io/kubernetes/pkg/util/runtime"
2015-08-05 22:03:47 +00:00
"k8s.io/kubernetes/pkg/util/strategicpatch"
2014-09-26 00:20:28 +00:00
2015-02-09 14:47:13 +00:00
"github.com/emicklei/go-restful"
2015-02-21 18:54:48 +00:00
"github.com/evanphx/json-patch"
2015-02-12 19:21:47 +00:00
"github.com/golang/glog"
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-12 19:21:47 +00:00
// ScopeNamer handles accessing names from requests and objects
type ScopeNamer interface {
// Namespace returns the appropriate namespace value from the request (may be empty) or an
// error.
Namespace ( req * restful . Request ) ( namespace string , err error )
// Name returns the name from the request, and an optional namespace value if this is a namespace
// scoped call. An error is returned if the name is not available.
Name ( req * restful . Request ) ( namespace , name string , err error )
// ObjectName returns the namespace and name from an object if they exist, or an error if the object
// does not support names.
ObjectName ( obj runtime . Object ) ( namespace , name string , err error )
// SetSelfLink sets the provided URL onto the object. The method should return nil if the object
// does not support selfLinks.
SetSelfLink ( obj runtime . Object , url string ) error
// GenerateLink creates a path and query for a given runtime object that represents the canonical path.
GenerateLink ( req * restful . Request , obj runtime . Object ) ( path , query string , err error )
// GenerateLink creates a path and query for a list that represents the canonical path.
GenerateListLink ( req * restful . Request ) ( path , query string , err error )
}
2014-08-09 21:12:55 +00:00
2015-03-22 03:25:38 +00:00
// RequestScope encapsulates common fields across all RESTful handler methods.
type RequestScope struct {
Namer ScopeNamer
ContextFunc
2016-04-05 03:07:43 +00:00
2016-04-20 17:35:09 +00:00
Serializer runtime . NegotiatedSerializer
2015-12-21 05:15:35 +00:00
runtime . ParameterCodec
2016-04-05 03:07:43 +00:00
2015-03-22 21:43:00 +00:00
Creater runtime . ObjectCreater
Convertor runtime . ObjectConvertor
2015-11-30 16:29:19 +00:00
Resource unversioned . GroupVersionResource
Kind unversioned . GroupVersionKind
Subresource string
2015-03-22 03:25:38 +00:00
}
2016-01-30 22:58:15 +00:00
func ( scope * RequestScope ) err ( err error , w http . ResponseWriter , req * http . Request ) {
errorNegotiated ( err , scope . Serializer , scope . Kind . GroupVersion ( ) , w , req )
2015-12-21 05:15:35 +00:00
}
2015-04-06 16:58:00 +00:00
// getterFunc performs a get request with the given context and object name. The request
// may be used to deserialize an options object to pass to the getter.
type getterFunc func ( ctx api . Context , name string , req * restful . Request ) ( runtime . Object , error )
2015-09-29 18:37:26 +00:00
// MaxPatchConflicts is the maximum number of conflicts retry for during a patch operation before returning failure
const MaxPatchConflicts = 5
2015-04-06 16:58:00 +00:00
// getResourceHandler is an HTTP handler function for get requests. It delegates to the
// passed-in getterFunc to perform the actual get.
func getResourceHandler ( scope RequestScope , getter getterFunc ) restful . RouteFunction {
2015-02-09 14:47:13 +00:00
return func ( req * restful . Request , res * restful . Response ) {
w := res . ResponseWriter
2015-03-22 03:25:38 +00:00
namespace , name , err := scope . Namer . Name ( req )
2015-02-09 14:47:13 +00:00
if err != nil {
2016-01-30 22:58:15 +00:00
scope . err ( err , res . ResponseWriter , req . Request )
2015-02-09 14:47:13 +00:00
return
}
2015-03-22 03:25:38 +00:00
ctx := scope . ContextFunc ( req )
2015-02-12 19:21:47 +00:00
ctx = api . WithNamespace ( ctx , namespace )
2015-04-06 16:58:00 +00:00
result , err := getter ( ctx , name , req )
2015-02-09 14:47:13 +00:00
if err != nil {
2016-01-30 22:58:15 +00:00
scope . err ( err , res . ResponseWriter , req . Request )
2015-02-09 14:47:13 +00:00
return
}
2015-03-22 03:25:38 +00:00
if err := setSelfLink ( result , req , scope . Namer ) ; err != nil {
2016-01-30 22:58:15 +00:00
scope . err ( err , res . ResponseWriter , req . Request )
2015-02-09 14:47:13 +00:00
return
2014-12-09 19:23:21 +00:00
}
2015-12-21 05:15:35 +00:00
write ( http . StatusOK , scope . Kind . GroupVersion ( ) , scope . Serializer , result , w , req . Request )
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-04-06 16:58:00 +00:00
// GetResource returns a function that handles retrieving a single resource from a rest.Storage object.
2015-12-01 23:45:29 +00:00
func GetResource ( r rest . Getter , e rest . Exporter , scope RequestScope ) restful . RouteFunction {
2015-04-06 16:58:00 +00:00
return getResourceHandler ( scope ,
func ( ctx api . Context , name string , req * restful . Request ) ( runtime . Object , error ) {
2015-12-03 11:31:22 +00:00
// For performance tracking purposes.
trace := util . NewTrace ( "Get " + req . Request . URL . Path )
defer trace . LogIfLong ( 250 * time . Millisecond )
2015-12-21 05:15:35 +00:00
// check for export
if values := req . Request . URL . Query ( ) ; len ( values ) > 0 {
// TODO: this is internal version, not unversioned
exports := unversioned . ExportOptions { }
if err := scope . ParameterCodec . DecodeParameters ( values , unversioned . GroupVersion { Version : "v1" } , & exports ) ; err != nil {
return nil , err
}
if exports . Export {
if e == nil {
return nil , errors . NewBadRequest ( fmt . Sprintf ( "export of %q is not supported" , scope . Resource . Resource ) )
}
return e . Export ( ctx , name , exports )
2015-12-01 23:45:29 +00:00
}
}
2015-12-21 05:15:35 +00:00
2015-04-06 16:58:00 +00:00
return r . Get ( ctx , name )
} )
}
// GetResourceWithOptions returns a function that handles retrieving a single resource from a rest.Storage object.
2015-12-21 05:15:35 +00:00
func GetResourceWithOptions ( r rest . GetterWithOptions , scope RequestScope ) restful . RouteFunction {
2015-04-06 16:58:00 +00:00
return getResourceHandler ( scope ,
func ( ctx api . Context , name string , req * restful . Request ) ( runtime . Object , error ) {
2015-12-21 05:15:35 +00:00
opts , subpath , subpathKey := r . NewGetOptions ( )
if err := getRequestOptions ( req , scope , opts , subpath , subpathKey ) ; err != nil {
2015-04-06 16:58:00 +00:00
return nil , err
}
return r . Get ( ctx , name , opts )
} )
}
2015-12-21 05:15:35 +00:00
func getRequestOptions ( req * restful . Request , scope RequestScope , into runtime . Object , subpath bool , subpathKey string ) error {
if into == nil {
return nil
2015-04-14 14:57:00 +00:00
}
2015-11-30 16:29:19 +00:00
2015-04-14 14:57:00 +00:00
query := req . Request . URL . Query ( )
if subpath {
newQuery := make ( url . Values )
for k , v := range query {
newQuery [ k ] = v
}
newQuery [ subpathKey ] = [ ] string { req . PathParameter ( "path" ) }
query = newQuery
}
2015-12-21 05:15:35 +00:00
return scope . ParameterCodec . DecodeParameters ( query , scope . Kind . GroupVersion ( ) , into )
2015-04-14 14:57:00 +00:00
}
// ConnectResource returns a function that handles a connect request on a rest.Storage object.
2015-12-21 05:15:35 +00:00
func ConnectResource ( connecter rest . Connecter , scope RequestScope , admit admission . Interface , restPath string ) restful . RouteFunction {
2015-04-14 14:57:00 +00:00
return func ( req * restful . Request , res * restful . Response ) {
w := res . ResponseWriter
namespace , name , err := scope . Namer . Name ( req )
if err != nil {
2016-01-30 22:58:15 +00:00
scope . err ( err , res . ResponseWriter , req . Request )
2015-04-14 14:57:00 +00:00
return
}
ctx := scope . ContextFunc ( req )
ctx = api . WithNamespace ( ctx , namespace )
2015-12-21 05:15:35 +00:00
opts , subpath , subpathKey := connecter . NewConnectOptions ( )
if err := getRequestOptions ( req , scope , opts , subpath , subpathKey ) ; err != nil {
2016-01-30 22:58:15 +00:00
scope . err ( err , res . ResponseWriter , req . Request )
2015-04-14 14:57:00 +00:00
return
}
2015-05-08 19:02:12 +00:00
if admit . Handles ( admission . Connect ) {
connectRequest := & rest . ConnectRequest {
Name : name ,
Options : opts ,
ResourcePath : restPath ,
}
userInfo , _ := api . UserFrom ( ctx )
2015-06-17 20:40:36 +00:00
2016-04-21 15:14:58 +00:00
err = admit . Admit ( admission . NewAttributesRecord ( connectRequest , scope . Kind , namespace , name , scope . Resource , scope . Subresource , admission . Connect , userInfo ) )
2015-05-08 19:02:12 +00:00
if err != nil {
2016-01-30 22:58:15 +00:00
scope . err ( err , res . ResponseWriter , req . Request )
2015-05-08 19:02:12 +00:00
return
}
}
2015-12-21 05:15:35 +00:00
handler , err := connecter . Connect ( ctx , name , opts , & responder { scope : scope , req : req , res : res } )
2015-04-14 14:57:00 +00:00
if err != nil {
2016-01-30 22:58:15 +00:00
scope . err ( err , res . ResponseWriter , req . Request )
2015-04-14 14:57:00 +00:00
return
}
handler . ServeHTTP ( w , req . Request )
}
}
2015-10-11 03:14:18 +00:00
// responder implements rest.Responder for assisting a connector in writing objects or errors.
type responder struct {
scope RequestScope
2015-12-21 05:15:35 +00:00
req * restful . Request
res * restful . Response
2015-10-11 03:14:18 +00:00
}
func ( r * responder ) Object ( statusCode int , obj runtime . Object ) {
2015-12-21 05:15:35 +00:00
write ( statusCode , r . scope . Kind . GroupVersion ( ) , r . scope . Serializer , obj , r . res . ResponseWriter , r . req . Request )
2015-10-11 03:14:18 +00:00
}
func ( r * responder ) Error ( err error ) {
2016-01-30 22:58:15 +00:00
r . scope . err ( err , r . res . ResponseWriter , r . req . Request )
2015-10-11 03:14:18 +00:00
}
2015-03-21 16:24:16 +00:00
// ListResource returns a function that handles retrieving a list of resources from a rest.Storage object.
2015-06-16 02:39:31 +00:00
func ListResource ( r rest . Lister , rw rest . Watcher , scope RequestScope , forceWatch bool , minRequestTimeout time . Duration ) restful . RouteFunction {
2015-02-09 14:47:13 +00:00
return func ( req * restful . Request , res * restful . Response ) {
2015-12-01 12:09:52 +00:00
// For performance tracking purposes.
trace := util . NewTrace ( "List " + req . Request . URL . Path )
2015-02-09 14:47:13 +00:00
w := res . ResponseWriter
2014-10-30 22:04:11 +00:00
2015-03-22 03:25:38 +00:00
namespace , err := scope . Namer . Namespace ( req )
2015-02-09 14:47:13 +00:00
if err != nil {
2016-01-30 22:58:15 +00:00
scope . err ( err , res . ResponseWriter , req . Request )
2015-02-09 14:47:13 +00:00
return
}
2015-04-23 23:02:22 +00:00
// Watches for single objects are routed to this function.
// Treat a /name parameter the same as a field selector entry.
hasName := true
_ , name , err := scope . Namer . Name ( req )
if err != nil {
hasName = false
}
2015-03-22 03:25:38 +00:00
ctx := scope . ContextFunc ( req )
2015-02-12 19:21:47 +00:00
ctx = api . WithNamespace ( ctx , namespace )
2015-12-16 13:02:09 +00:00
opts := api . ListOptions { }
2015-12-21 05:15:35 +00:00
if err := scope . ParameterCodec . DecodeParameters ( req . Request . URL . Query ( ) , scope . Kind . GroupVersion ( ) , & opts ) ; err != nil {
2016-01-30 22:58:15 +00:00
scope . err ( err , res . ResponseWriter , req . Request )
2015-11-10 09:20:57 +00:00
return
}
2015-03-22 21:43:00 +00:00
// transform fields
2015-11-16 13:59:01 +00:00
// TODO: DecodeParametersInto should do this.
2015-12-16 13:02:09 +00:00
if opts . FieldSelector != nil {
2015-11-16 13:59:01 +00:00
fn := func ( label , value string ) ( newLabel , newValue string , err error ) {
2015-11-30 16:29:19 +00:00
return scope . Convertor . ConvertFieldLabel ( scope . Kind . GroupVersion ( ) . String ( ) , scope . Kind . Kind , label , value )
2015-11-16 13:59:01 +00:00
}
2015-12-16 13:02:09 +00:00
if opts . FieldSelector , err = opts . FieldSelector . Transform ( fn ) ; err != nil {
2015-11-16 13:59:01 +00:00
// TODO: allow bad request to set field causes based on query parameters
err = errors . NewBadRequest ( err . Error ( ) )
2016-01-30 22:58:15 +00:00
scope . err ( err , res . ResponseWriter , req . Request )
2015-11-16 13:59:01 +00:00
return
}
2014-10-30 22:04:11 +00:00
}
2014-09-26 00:20:28 +00:00
2015-04-23 23:02:22 +00:00
if hasName {
// metadata.name is the canonical internal name.
// generic.SelectionPredicate will notice that this is
// a request for a single object and optimize the
// storage query accordingly.
nameSelector := fields . OneTermEqualSelector ( "metadata.name" , name )
2015-12-16 13:02:09 +00:00
if opts . FieldSelector != nil && ! opts . FieldSelector . Empty ( ) {
2015-04-23 23:02:22 +00:00
// It doesn't make sense to ask for both a name
// and a field selector, since just the name is
// sufficient to narrow down the request to a
// single object.
2016-01-30 22:58:15 +00:00
scope . err ( errors . NewBadRequest ( "both a name and a field selector provided; please provide one or the other." ) , res . ResponseWriter , req . Request )
2015-04-23 23:02:22 +00:00
return
}
2015-12-16 13:02:09 +00:00
opts . FieldSelector = nameSelector
2015-04-23 23:02:22 +00:00
}
2015-03-24 04:07:22 +00:00
if ( opts . Watch || forceWatch ) && rw != nil {
2015-10-27 13:47:58 +00:00
watcher , err := rw . Watch ( ctx , & opts )
2015-03-24 04:07:22 +00:00
if err != nil {
2016-01-30 22:58:15 +00:00
scope . err ( err , res . ResponseWriter , req . Request )
2015-03-24 04:07:22 +00:00
return
}
2015-10-16 13:07:14 +00:00
// TODO: Currently we explicitly ignore ?timeout= and use only ?timeoutSeconds=.
timeout := time . Duration ( 0 )
if opts . TimeoutSeconds != nil {
timeout = time . Duration ( * opts . TimeoutSeconds ) * time . Second
}
if timeout == 0 && minRequestTimeout > 0 {
timeout = time . Duration ( float64 ( minRequestTimeout ) * ( rand . Float64 ( ) + 1.0 ) )
}
2015-12-21 05:15:35 +00:00
serveWatch ( watcher , scope , req , res , timeout )
2015-03-24 04:07:22 +00:00
return
}
2015-12-01 12:09:52 +00:00
// Log only long List requests (ignore Watch).
defer trace . LogIfLong ( 500 * time . Millisecond )
trace . Step ( "About to List from storage" )
2015-10-27 13:47:58 +00:00
result , err := r . List ( ctx , & opts )
2015-02-09 14:47:13 +00:00
if err != nil {
2016-01-30 22:58:15 +00:00
scope . err ( err , res . ResponseWriter , req . Request )
2015-02-09 14:47:13 +00:00
return
2014-12-09 19:23:21 +00:00
}
2015-12-01 12:09:52 +00:00
trace . Step ( "Listing from storage done" )
numberOfItems , err := setListSelfLink ( result , req , scope . Namer )
if err != nil {
2016-01-30 22:58:15 +00:00
scope . err ( err , res . ResponseWriter , req . Request )
2015-02-09 14:47:13 +00:00
return
}
2015-12-01 12:09:52 +00:00
trace . Step ( "Self-linking done" )
2015-12-21 05:15:35 +00:00
write ( http . StatusOK , scope . Kind . GroupVersion ( ) , scope . Serializer , result , w , req . Request )
2015-12-01 12:09:52 +00:00
trace . Step ( fmt . Sprintf ( "Writing http response done (%d items)" , numberOfItems ) )
2014-11-24 18:35:24 +00:00
}
2014-09-26 00:20:28 +00:00
}
2015-05-04 18:38:41 +00:00
func createHandler ( r rest . NamedCreater , scope RequestScope , typer runtime . ObjectTyper , admit admission . Interface , includeName bool ) restful . RouteFunction {
2015-02-09 14:47:13 +00:00
return func ( req * restful . Request , res * restful . Response ) {
2015-12-03 11:31:22 +00:00
// For performance tracking purposes.
trace := util . NewTrace ( "Create " + req . Request . URL . Path )
defer trace . LogIfLong ( 250 * time . Millisecond )
2015-02-09 14:47:13 +00:00
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" ) )
2015-05-04 18:38:41 +00:00
var (
namespace , name string
err error
)
if includeName {
namespace , name , err = scope . Namer . Name ( req )
} else {
namespace , err = scope . Namer . Namespace ( req )
}
2015-02-09 14:47:13 +00:00
if err != nil {
2016-01-30 22:58:15 +00:00
scope . err ( err , res . ResponseWriter , req . Request )
2015-02-09 14:47:13 +00:00
return
}
2015-05-04 18:38:41 +00:00
2015-03-22 03:25:38 +00:00
ctx := scope . ContextFunc ( req )
2015-02-12 19:21:47 +00:00
ctx = api . WithNamespace ( ctx , namespace )
2014-09-26 00:20:28 +00:00
2015-12-21 05:15:35 +00:00
gv := scope . Kind . GroupVersion ( )
s , err := negotiateInputSerializer ( req . Request , scope . Serializer )
if err != nil {
2016-01-30 22:58:15 +00:00
scope . err ( err , res . ResponseWriter , req . Request )
2015-12-21 05:15:35 +00:00
return
}
decoder := scope . Serializer . DecoderToVersion ( s , unversioned . GroupVersion { Group : gv . Group , Version : runtime . APIVersionInternal } )
2015-02-09 14:47:13 +00:00
body , err := readBody ( req . Request )
if err != nil {
2016-01-30 22:58:15 +00:00
scope . err ( err , res . ResponseWriter , req . Request )
2015-02-09 14:47:13 +00:00
return
2014-08-09 21:12:55 +00:00
}
2015-12-21 05:15:35 +00:00
defaultGVK := scope . Kind
original := r . New ( )
2015-12-03 11:31:22 +00:00
trace . Step ( "About to convert to expected version" )
2015-12-21 05:15:35 +00:00
obj , gvk , err := decoder . Decode ( body , & defaultGVK , original )
if err != nil {
2016-05-09 15:54:43 +00:00
err = transformDecodeError ( typer , err , original , gvk , body )
2016-01-30 22:58:15 +00:00
scope . err ( err , res . ResponseWriter , req . Request )
2015-12-21 05:15:35 +00:00
return
}
if gvk . GroupVersion ( ) != gv {
err = errors . NewBadRequest ( fmt . Sprintf ( "the API version in the data (%s) does not match the expected API version (%v)" , gvk . GroupVersion ( ) . String ( ) , gv . String ( ) ) )
2016-01-30 22:58:15 +00:00
scope . err ( err , res . ResponseWriter , req . Request )
2015-02-09 14:47:13 +00:00
return
2014-08-09 21:12:55 +00:00
}
2015-12-03 11:31:22 +00:00
trace . Step ( "Conversion done" )
2015-02-09 14:47:13 +00:00
2015-08-20 05:08:26 +00:00
if admit != nil && admit . Handles ( admission . Create ) {
2015-05-15 14:48:33 +00:00
userInfo , _ := api . UserFrom ( ctx )
2015-06-17 20:40:36 +00:00
2016-04-21 15:14:58 +00:00
err = admit . Admit ( admission . NewAttributesRecord ( obj , scope . Kind , namespace , name , scope . Resource , scope . Subresource , admission . Create , userInfo ) )
2015-05-15 14:48:33 +00:00
if err != nil {
2016-01-30 22:58:15 +00:00
scope . err ( err , res . ResponseWriter , req . Request )
2015-05-15 14:48:33 +00:00
return
}
2015-01-12 05:33:25 +00:00
}
2015-12-03 11:31:22 +00:00
trace . Step ( "About to store object in database" )
2015-02-10 14:26:26 +00:00
result , err := finishRequest ( timeout , func ( ) ( runtime . Object , error ) {
2015-05-04 18:38:41 +00:00
out , err := r . Create ( ctx , name , obj )
2015-09-09 21:59:11 +00:00
if status , ok := out . ( * unversioned . Status ) ; ok && err == nil && status . Code == 0 {
2015-02-11 23:33:14 +00:00
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 {
2016-01-30 22:58:15 +00:00
scope . err ( err , res . ResponseWriter , req . Request )
2015-02-09 14:47:13 +00:00
return
2014-08-09 21:12:55 +00:00
}
2015-12-03 11:31:22 +00:00
trace . Step ( "Object stored in database" )
2015-02-09 14:47:13 +00:00
2015-03-22 03:25:38 +00:00
if err := setSelfLink ( result , req , scope . Namer ) ; err != nil {
2016-01-30 22:58:15 +00:00
scope . err ( err , res . ResponseWriter , req . Request )
2015-02-09 14:47:13 +00:00
return
2014-08-09 21:12:55 +00:00
}
2015-12-03 11:31:22 +00:00
trace . Step ( "Self-link added" )
2015-01-06 16:44:43 +00:00
2015-12-21 05:15:35 +00:00
write ( http . StatusCreated , scope . Kind . GroupVersion ( ) , scope . Serializer , result , w , req . Request )
2015-02-09 14:47:13 +00:00
}
}
2015-05-04 18:38:41 +00:00
// CreateNamedResource returns a function that will handle a resource creation with name.
func CreateNamedResource ( r rest . NamedCreater , scope RequestScope , typer runtime . ObjectTyper , admit admission . Interface ) restful . RouteFunction {
return createHandler ( r , scope , typer , admit , true )
}
// CreateResource returns a function that will handle a resource creation.
func CreateResource ( r rest . Creater , scope RequestScope , typer runtime . ObjectTyper , admit admission . Interface ) restful . RouteFunction {
return createHandler ( & namedCreaterAdapter { r } , scope , typer , admit , false )
}
type namedCreaterAdapter struct {
rest . Creater
}
func ( c * namedCreaterAdapter ) Create ( ctx api . Context , name string , obj runtime . Object ) ( runtime . Object , error ) {
return c . Creater . Create ( ctx , obj )
}
2015-02-21 18:54:48 +00:00
// PatchResource returns a function that will handle a resource patch
2015-04-12 22:22:04 +00:00
// TODO: Eventually PatchResource should just use GuaranteedUpdate and this routine should be a bit cleaner
2015-03-14 00:43:14 +00:00
func PatchResource ( r rest . Patcher , scope RequestScope , typer runtime . ObjectTyper , admit admission . Interface , converter runtime . ObjectConvertor ) restful . RouteFunction {
2015-02-21 18:54:48 +00:00
return func ( req * restful . Request , res * restful . Response ) {
w := res . ResponseWriter
2015-03-14 00:43:14 +00:00
// 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)
2015-02-21 18:54:48 +00:00
timeout := parseTimeout ( req . Request . URL . Query ( ) . Get ( "timeout" ) )
2015-03-22 03:25:38 +00:00
namespace , name , err := scope . Namer . Name ( req )
2015-02-21 18:54:48 +00:00
if err != nil {
2016-01-30 22:58:15 +00:00
scope . err ( err , res . ResponseWriter , req . Request )
2015-02-21 18:54:48 +00:00
return
}
2015-05-14 01:31:51 +00:00
ctx := scope . ContextFunc ( req )
ctx = api . WithNamespace ( ctx , namespace )
2016-05-01 00:43:30 +00:00
versionedObj , err := converter . ConvertToVersion ( r . New ( ) , scope . Kind . GroupVersion ( ) )
2015-03-14 00:43:14 +00:00
if err != nil {
2016-01-30 22:58:15 +00:00
scope . err ( err , res . ResponseWriter , req . Request )
2015-03-14 00:43:14 +00:00
return
}
2015-12-21 05:15:35 +00:00
// TODO: handle this in negotiation
2015-09-29 18:37:26 +00:00
contentType := req . HeaderParameter ( "Content-Type" )
// Remove "; charset=" if included in header.
if idx := strings . Index ( contentType , ";" ) ; idx > 0 {
contentType = contentType [ : idx ]
2015-02-21 18:54:48 +00:00
}
2015-09-29 18:37:26 +00:00
patchType := api . PatchType ( contentType )
2015-02-21 18:54:48 +00:00
2015-03-14 00:43:14 +00:00
patchJS , err := readBody ( req . Request )
2015-02-21 18:54:48 +00:00
if err != nil {
2016-01-30 22:58:15 +00:00
scope . err ( err , res . ResponseWriter , req . Request )
2015-12-21 05:15:35 +00:00
return
}
s , ok := scope . Serializer . SerializerForMediaType ( "application/json" , nil )
if ! ok {
2016-01-30 22:58:15 +00:00
scope . err ( fmt . Errorf ( "no serializer defined for JSON" ) , res . ResponseWriter , req . Request )
2015-02-21 18:54:48 +00:00
return
}
2015-12-21 05:15:35 +00:00
gv := scope . Kind . GroupVersion ( )
codec := runtime . NewCodec (
scope . Serializer . EncoderForVersion ( s , gv ) ,
scope . Serializer . DecoderToVersion ( s , unversioned . GroupVersion { Group : gv . Group , Version : runtime . APIVersionInternal } ) ,
)
2015-02-21 18:54:48 +00:00
2016-01-11 17:54:26 +00:00
updateAdmit := func ( updatedObject runtime . Object ) error {
if admit != nil && admit . Handles ( admission . Update ) {
userInfo , _ := api . UserFrom ( ctx )
2016-04-21 15:14:58 +00:00
return admit . Admit ( admission . NewAttributesRecord ( updatedObject , scope . Kind , namespace , name , scope . Resource , scope . Subresource , admission . Update , userInfo ) )
2016-01-11 17:54:26 +00:00
}
return nil
}
2015-12-21 05:15:35 +00:00
result , err := patchResource ( ctx , updateAdmit , timeout , versionedObj , r , name , patchType , patchJS , scope . Namer , codec )
2015-02-21 18:54:48 +00:00
if err != nil {
2016-01-30 22:58:15 +00:00
scope . err ( err , res . ResponseWriter , req . Request )
2015-02-21 18:54:48 +00:00
return
}
2015-03-22 03:25:38 +00:00
if err := setSelfLink ( result , req , scope . Namer ) ; err != nil {
2016-01-30 22:58:15 +00:00
scope . err ( err , res . ResponseWriter , req . Request )
2015-02-21 18:54:48 +00:00
return
}
2015-12-21 05:15:35 +00:00
write ( http . StatusOK , scope . Kind . GroupVersion ( ) , scope . Serializer , result , w , req . Request )
2015-02-21 18:54:48 +00:00
}
2015-09-29 18:37:26 +00:00
}
2016-01-11 17:54:26 +00:00
type updateAdmissionFunc func ( updatedObject runtime . Object ) error
2015-09-29 18:37:26 +00:00
// patchResource divides PatchResource for easier unit testing
2016-01-11 17:54:26 +00:00
func patchResource ( ctx api . Context , admit updateAdmissionFunc , timeout time . Duration , versionedObj runtime . Object , patcher rest . Patcher , name string , patchType api . PatchType , patchJS [ ] byte , namer ScopeNamer , codec runtime . Codec ) ( runtime . Object , error ) {
2015-09-29 18:37:26 +00:00
namespace := api . NamespaceValue ( ctx )
original , err := patcher . Get ( ctx , name )
if err != nil {
return nil , err
}
2015-12-10 02:15:02 +00:00
originalObjJS , err := runtime . Encode ( codec , original )
2015-09-29 18:37:26 +00:00
if err != nil {
return nil , err
}
originalPatchedObjJS , err := getPatchedJS ( patchType , originalObjJS , patchJS , versionedObj )
if err != nil {
return nil , err
}
objToUpdate := patcher . New ( )
2015-12-10 02:15:02 +00:00
if err := runtime . DecodeInto ( codec , originalPatchedObjJS , objToUpdate ) ; err != nil {
2015-09-29 18:37:26 +00:00
return nil , err
}
if err := checkName ( objToUpdate , name , namespace , namer ) ; err != nil {
return nil , err
}
return finishRequest ( timeout , func ( ) ( runtime . Object , error ) {
2016-01-11 17:54:26 +00:00
if err := admit ( objToUpdate ) ; err != nil {
return nil , err
}
2015-09-29 18:37:26 +00:00
// update should never create as previous get would fail
updateObject , _ , updateErr := patcher . Update ( ctx , objToUpdate )
for i := 0 ; i < MaxPatchConflicts && ( errors . IsConflict ( updateErr ) ) ; i ++ {
// on a conflict,
// 1. build a strategic merge patch from originalJS and the patchedJS. Different patch types can
// be specified, but a strategic merge patch should be expressive enough handle them. Build the
// patch with this type to handle those cases.
// 2. build a strategic merge patch from originalJS and the currentJS
// 3. ensure no conflicts between the two patches
// 4. apply the #1 patch to the currentJS object
// 5. retry the update
currentObject , err := patcher . Get ( ctx , name )
if err != nil {
return nil , err
}
2015-12-10 02:15:02 +00:00
currentObjectJS , err := runtime . Encode ( codec , currentObject )
2015-09-29 18:37:26 +00:00
if err != nil {
return nil , err
}
2016-03-17 20:19:05 +00:00
currentPatch , err := strategicpatch . CreateStrategicMergePatch ( originalObjJS , currentObjectJS , versionedObj )
2015-09-29 18:37:26 +00:00
if err != nil {
return nil , err
}
2016-03-17 20:19:05 +00:00
originalPatch , err := strategicpatch . CreateStrategicMergePatch ( originalObjJS , originalPatchedObjJS , versionedObj )
2015-09-29 18:37:26 +00:00
if err != nil {
return nil , err
}
diff1 := make ( map [ string ] interface { } )
if err := json . Unmarshal ( originalPatch , & diff1 ) ; err != nil {
return nil , err
}
diff2 := make ( map [ string ] interface { } )
if err := json . Unmarshal ( currentPatch , & diff2 ) ; err != nil {
return nil , err
}
2015-09-30 14:39:08 +00:00
hasConflicts , err := strategicpatch . HasConflicts ( diff1 , diff2 )
if err != nil {
return nil , err
}
if hasConflicts {
2016-02-23 00:41:34 +00:00
glog . V ( 4 ) . Infof ( "patchResource failed for resource %s, becauase there is a meaningful conflict.\n diff1=%v\n, diff2=%v\n" , name , diff1 , diff2 )
2015-09-29 18:37:26 +00:00
return updateObject , updateErr
}
newlyPatchedObjJS , err := getPatchedJS ( api . StrategicMergePatchType , currentObjectJS , originalPatch , versionedObj )
if err != nil {
return nil , err
}
2015-12-10 02:15:02 +00:00
if err := runtime . DecodeInto ( codec , newlyPatchedObjJS , objToUpdate ) ; err != nil {
2015-09-29 18:37:26 +00:00
return nil , err
}
2016-01-11 17:54:26 +00:00
if err := admit ( objToUpdate ) ; err != nil {
return nil , err
}
2015-09-29 18:37:26 +00:00
updateObject , _ , updateErr = patcher . Update ( ctx , objToUpdate )
}
return updateObject , updateErr
} )
}
2015-02-09 14:47:13 +00:00
// UpdateResource returns a function that will handle a resource update
2015-03-22 03:25:38 +00:00
func UpdateResource ( r rest . Updater , scope RequestScope , typer runtime . ObjectTyper , admit admission . Interface ) restful . RouteFunction {
2015-02-09 14:47:13 +00:00
return func ( req * restful . Request , res * restful . Response ) {
2015-12-03 11:31:22 +00:00
// For performance tracking purposes.
trace := util . NewTrace ( "Update " + req . Request . URL . Path )
defer trace . LogIfLong ( 250 * time . Millisecond )
2015-02-09 14:47:13 +00:00
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" ) )
2015-03-22 03:25:38 +00:00
namespace , name , err := scope . Namer . Name ( req )
2015-01-06 16:44:43 +00:00
if err != nil {
2016-01-30 22:58:15 +00:00
scope . err ( err , res . ResponseWriter , req . Request )
2015-02-09 14:47:13 +00:00
return
}
2015-03-22 03:25:38 +00:00
ctx := scope . ContextFunc ( req )
2015-02-12 19:21:47 +00:00
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 {
2016-01-30 22:58:15 +00:00
scope . err ( err , res . ResponseWriter , req . Request )
2015-02-09 14:47:13 +00:00
return
2014-08-09 21:12:55 +00:00
}
2015-12-21 05:15:35 +00:00
s , err := negotiateInputSerializer ( req . Request , scope . Serializer )
if err != nil {
2016-01-30 22:58:15 +00:00
scope . err ( err , res . ResponseWriter , req . Request )
2015-12-21 05:15:35 +00:00
return
}
defaultGVK := scope . Kind
original := r . New ( )
2015-12-03 11:31:22 +00:00
trace . Step ( "About to convert to expected version" )
2015-12-21 05:15:35 +00:00
obj , gvk , err := scope . Serializer . DecoderToVersion ( s , defaultGVK . GroupVersion ( ) ) . Decode ( body , & defaultGVK , original )
if err != nil {
2016-05-09 15:54:43 +00:00
err = transformDecodeError ( typer , err , original , gvk , body )
2016-01-30 22:58:15 +00:00
scope . err ( err , res . ResponseWriter , req . Request )
2015-12-21 05:15:35 +00:00
return
}
if gvk . GroupVersion ( ) != defaultGVK . GroupVersion ( ) {
err = errors . NewBadRequest ( fmt . Sprintf ( "the API version in the data (%s) does not match the expected API version (%s)" , gvk . GroupVersion ( ) , defaultGVK . GroupVersion ( ) ) )
2016-01-30 22:58:15 +00:00
scope . err ( err , res . ResponseWriter , req . Request )
2015-02-09 14:47:13 +00:00
return
2014-08-09 21:12:55 +00:00
}
2015-12-03 11:31:22 +00:00
trace . Step ( "Conversion done" )
2015-02-09 14:47:13 +00:00
2015-03-22 03:25:38 +00:00
if err := checkName ( obj , name , namespace , scope . Namer ) ; err != nil {
2016-01-30 22:58:15 +00:00
scope . err ( err , res . ResponseWriter , req . Request )
2015-02-21 18:54:48 +00:00
return
2015-01-12 05:33:25 +00:00
}
2015-01-06 16:44:43 +00:00
2015-08-20 05:08:26 +00:00
if admit != nil && admit . Handles ( admission . Update ) {
2015-05-15 14:48:33 +00:00
userInfo , _ := api . UserFrom ( ctx )
2015-06-17 20:40:36 +00:00
2016-04-21 15:14:58 +00:00
err = admit . Admit ( admission . NewAttributesRecord ( obj , scope . Kind , namespace , name , scope . Resource , scope . Subresource , admission . Update , userInfo ) )
2015-05-15 14:48:33 +00:00
if err != nil {
2016-01-30 22:58:15 +00:00
scope . err ( err , res . ResponseWriter , req . Request )
2015-05-15 14:48:33 +00:00
return
}
2015-01-06 16:44:43 +00:00
}
2015-12-03 11:31:22 +00:00
trace . Step ( "About to store object in database" )
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 {
2016-01-30 22:58:15 +00:00
scope . err ( err , res . ResponseWriter , req . Request )
2015-02-09 14:47:13 +00:00
return
2014-08-09 21:12:55 +00:00
}
2015-12-03 11:31:22 +00:00
trace . Step ( "Object stored in database" )
2015-02-09 14:47:13 +00:00
2015-03-22 03:25:38 +00:00
if err := setSelfLink ( result , req , scope . Namer ) ; err != nil {
2016-01-30 22:58:15 +00:00
scope . err ( err , res . ResponseWriter , req . Request )
2015-02-09 14:47:13 +00:00
return
2015-01-12 05:33:25 +00:00
}
2015-12-03 11:31:22 +00:00
trace . Step ( "Self-link added" )
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-12-21 05:15:35 +00:00
write ( status , scope . Kind . GroupVersion ( ) , scope . Serializer , result , w , req . Request )
2015-02-09 14:47:13 +00:00
}
}
// DeleteResource returns a function that will handle a resource deletion
2015-03-22 03:25:38 +00:00
func DeleteResource ( r rest . GracefulDeleter , checkBody bool , scope RequestScope , admit admission . Interface ) restful . RouteFunction {
2015-02-09 14:47:13 +00:00
return func ( req * restful . Request , res * restful . Response ) {
2015-12-03 11:31:22 +00:00
// For performance tracking purposes.
trace := util . NewTrace ( "Delete " + req . Request . URL . Path )
defer trace . LogIfLong ( 250 * time . Millisecond )
2015-02-09 14:47:13 +00:00
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" ) )
2015-03-22 03:25:38 +00:00
namespace , name , err := scope . Namer . Name ( req )
2014-08-09 21:12:55 +00:00
if err != nil {
2016-01-30 22:58:15 +00:00
scope . err ( err , res . ResponseWriter , req . Request )
2015-02-09 14:47:13 +00:00
return
2014-08-09 21:12:55 +00:00
}
2015-03-22 03:25:38 +00:00
ctx := scope . ContextFunc ( req )
ctx = api . WithNamespace ( ctx , namespace )
2015-02-09 14:47:13 +00:00
2015-03-05 03:34:31 +00:00
options := & api . DeleteOptions { }
if checkBody {
body , err := readBody ( req . Request )
if err != nil {
2016-01-30 22:58:15 +00:00
scope . err ( err , res . ResponseWriter , req . Request )
2015-03-05 03:34:31 +00:00
return
}
if len ( body ) > 0 {
2015-12-21 05:15:35 +00:00
s , err := negotiateInputSerializer ( req . Request , scope . Serializer )
if err != nil {
2016-01-30 22:58:15 +00:00
scope . err ( err , res . ResponseWriter , req . Request )
2015-12-21 05:15:35 +00:00
return
}
defaultGVK := scope . Kind . GroupVersion ( ) . WithKind ( "DeleteOptions" )
obj , _ , err := scope . Serializer . DecoderToVersion ( s , defaultGVK . GroupVersion ( ) ) . Decode ( body , & defaultGVK , options )
if err != nil {
2016-01-30 22:58:15 +00:00
scope . err ( err , res . ResponseWriter , req . Request )
2015-12-21 05:15:35 +00:00
return
}
if obj != options {
2016-01-30 22:58:15 +00:00
scope . err ( fmt . Errorf ( "decoded object cannot be converted to DeleteOptions" ) , res . ResponseWriter , req . Request )
2015-03-05 03:34:31 +00:00
return
}
}
}
2015-08-20 05:08:26 +00:00
if admit != nil && admit . Handles ( admission . Delete ) {
2015-05-15 14:48:33 +00:00
userInfo , _ := api . UserFrom ( ctx )
2015-06-17 20:40:36 +00:00
2016-04-21 15:14:58 +00:00
err = admit . Admit ( admission . NewAttributesRecord ( nil , scope . Kind , namespace , name , scope . Resource , scope . Subresource , admission . Delete , userInfo ) )
2015-05-15 14:48:33 +00:00
if err != nil {
2016-01-30 22:58:15 +00:00
scope . err ( err , res . ResponseWriter , req . Request )
2015-05-15 14:48:33 +00:00
return
}
2014-08-09 21:12:55 +00:00
}
2015-01-06 16:44:43 +00:00
2015-12-03 11:31:22 +00:00
trace . Step ( "About do delete object from database" )
2015-02-10 14:26:26 +00:00
result , err := finishRequest ( timeout , func ( ) ( runtime . Object , error ) {
2015-03-05 03:34:31 +00:00
return r . Delete ( ctx , name , options )
2015-02-10 14:26:26 +00:00
} )
2014-08-09 21:12:55 +00:00
if err != nil {
2016-01-30 22:58:15 +00:00
scope . err ( err , res . ResponseWriter , req . Request )
2015-02-09 14:47:13 +00:00
return
2014-08-09 21:12:55 +00:00
}
2015-12-03 11:31:22 +00:00
trace . Step ( "Object deleted from database" )
2014-08-09 21:12:55 +00:00
2015-03-21 16:24:16 +00:00
// if the rest.Deleter returns a nil object, fill out a status. Callers may return a valid
2015-02-09 14:47:13 +00:00
// object with the response.
2015-02-10 14:26:26 +00:00
if result == nil {
2015-09-09 21:59:11 +00:00
result = & unversioned . Status {
Status : unversioned . StatusSuccess ,
2015-02-09 14:47:13 +00:00
Code : http . StatusOK ,
2015-09-09 21:59:11 +00:00
Details : & unversioned . StatusDetails {
2015-06-05 13:45:59 +00:00
Name : name ,
2015-11-30 16:29:19 +00:00
Kind : scope . Kind . Kind ,
2015-02-09 14:47:13 +00:00
} ,
}
2015-02-10 14:26:26 +00:00
} else {
// when a non-status response is returned, set the self link
2015-09-09 21:59:11 +00:00
if _ , ok := result . ( * unversioned . Status ) ; ! ok {
2015-03-22 03:25:38 +00:00
if err := setSelfLink ( result , req , scope . Namer ) ; err != nil {
2016-01-30 22:58:15 +00:00
scope . err ( err , res . ResponseWriter , req . Request )
2015-12-07 09:21:42 +00:00
return
}
}
}
2015-12-21 05:15:35 +00:00
write ( http . StatusOK , scope . Kind . GroupVersion ( ) , scope . Serializer , result , w , req . Request )
2015-12-07 09:21:42 +00:00
}
}
// DeleteCollection returns a function that will handle a collection deletion
func DeleteCollection ( r rest . CollectionDeleter , checkBody bool , scope RequestScope , admit admission . Interface ) restful . RouteFunction {
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 := scope . Namer . Namespace ( req )
if err != nil {
2016-01-30 22:58:15 +00:00
scope . err ( err , res . ResponseWriter , req . Request )
2015-12-07 09:21:42 +00:00
return
}
ctx := scope . ContextFunc ( req )
ctx = api . WithNamespace ( ctx , namespace )
if admit != nil && admit . Handles ( admission . Delete ) {
userInfo , _ := api . UserFrom ( ctx )
2016-04-21 15:14:58 +00:00
err = admit . Admit ( admission . NewAttributesRecord ( nil , scope . Kind , namespace , "" , scope . Resource , scope . Subresource , admission . Delete , userInfo ) )
2015-12-07 09:21:42 +00:00
if err != nil {
2016-01-30 22:58:15 +00:00
scope . err ( err , res . ResponseWriter , req . Request )
2015-12-07 09:21:42 +00:00
return
}
}
2015-12-16 13:02:09 +00:00
listOptions := api . ListOptions { }
2015-12-21 05:15:35 +00:00
if err := scope . ParameterCodec . DecodeParameters ( req . Request . URL . Query ( ) , scope . Kind . GroupVersion ( ) , & listOptions ) ; err != nil {
2016-01-30 22:58:15 +00:00
scope . err ( err , res . ResponseWriter , req . Request )
2015-12-07 09:21:42 +00:00
return
}
// transform fields
// TODO: DecodeParametersInto should do this.
2015-12-16 13:02:09 +00:00
if listOptions . FieldSelector != nil {
2015-12-07 09:21:42 +00:00
fn := func ( label , value string ) ( newLabel , newValue string , err error ) {
return scope . Convertor . ConvertFieldLabel ( scope . Kind . GroupVersion ( ) . String ( ) , scope . Kind . Kind , label , value )
}
2015-12-16 13:02:09 +00:00
if listOptions . FieldSelector , err = listOptions . FieldSelector . Transform ( fn ) ; err != nil {
2015-12-07 09:21:42 +00:00
// TODO: allow bad request to set field causes based on query parameters
err = errors . NewBadRequest ( err . Error ( ) )
2016-01-30 22:58:15 +00:00
scope . err ( err , res . ResponseWriter , req . Request )
2015-12-07 09:21:42 +00:00
return
}
}
options := & api . DeleteOptions { }
if checkBody {
body , err := readBody ( req . Request )
if err != nil {
2016-01-30 22:58:15 +00:00
scope . err ( err , res . ResponseWriter , req . Request )
2015-12-07 09:21:42 +00:00
return
}
if len ( body ) > 0 {
2015-12-21 05:15:35 +00:00
s , err := negotiateInputSerializer ( req . Request , scope . Serializer )
if err != nil {
2016-01-30 22:58:15 +00:00
scope . err ( err , res . ResponseWriter , req . Request )
2015-12-21 05:15:35 +00:00
return
}
defaultGVK := scope . Kind . GroupVersion ( ) . WithKind ( "DeleteOptions" )
obj , _ , err := scope . Serializer . DecoderToVersion ( s , defaultGVK . GroupVersion ( ) ) . Decode ( body , & defaultGVK , options )
if err != nil {
2016-01-30 22:58:15 +00:00
scope . err ( err , res . ResponseWriter , req . Request )
2015-12-21 05:15:35 +00:00
return
}
if obj != options {
2016-01-30 22:58:15 +00:00
scope . err ( fmt . Errorf ( "decoded object cannot be converted to DeleteOptions" ) , res . ResponseWriter , req . Request )
2015-12-07 09:21:42 +00:00
return
}
}
}
result , err := finishRequest ( timeout , func ( ) ( runtime . Object , error ) {
return r . DeleteCollection ( ctx , options , & listOptions )
} )
if err != nil {
2016-01-30 22:58:15 +00:00
scope . err ( err , res . ResponseWriter , req . Request )
2015-12-07 09:21:42 +00:00
return
}
// if the rest.Deleter returns a nil object, fill out a status. Callers may return a valid
// object with the response.
if result == nil {
result = & unversioned . Status {
Status : unversioned . StatusSuccess ,
Code : http . StatusOK ,
Details : & unversioned . StatusDetails {
Kind : scope . Kind . Kind ,
} ,
}
} else {
// when a non-status response is returned, set the self link
if _ , ok := result . ( * unversioned . Status ) ; ! ok {
if _ , err := setListSelfLink ( result , req , scope . Namer ) ; err != nil {
2016-01-30 22:58:15 +00:00
scope . err ( err , res . ResponseWriter , req . Request )
2015-02-10 14:26:26 +00:00
return
}
}
2015-02-09 14:47:13 +00:00
}
2015-12-21 05:15:35 +00:00
writeNegotiated ( scope . Serializer , scope . Kind . GroupVersion ( ) , w , req . Request , http . StatusOK , result )
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 ) {
2015-03-11 19:51:20 +00:00
// these channels need to be buffered to prevent the goroutine below from hanging indefinitely
// when the select statement reads something other than the one the goroutine sends on.
ch := make ( chan runtime . Object , 1 )
errCh := make ( chan error , 1 )
2015-08-28 18:01:31 +00:00
panicCh := make ( chan interface { } , 1 )
2015-02-10 14:26:26 +00:00
go func ( ) {
2015-08-28 18:01:31 +00:00
// panics don't cross goroutine boundaries, so we have to handle ourselves
2016-01-15 07:32:10 +00:00
defer utilruntime . HandleCrash ( func ( panicReason interface { } ) {
2015-08-28 18:01:31 +00:00
// Propagate to parent goroutine
panicCh <- panicReason
} )
2015-02-10 14:26:26 +00:00
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 :
2015-09-09 21:59:11 +00:00
if status , ok := result . ( * unversioned . 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-08-28 18:01:31 +00:00
case p := <- panicCh :
panic ( p )
2015-02-09 14:47:13 +00:00
case <- time . After ( timeout ) :
2015-03-24 02:56:22 +00:00
return nil , errors . NewTimeoutError ( "request did not complete within allowed duration" , 0 )
2015-02-09 14:47:13 +00:00
}
2014-08-09 21:12:55 +00:00
}
2015-03-04 20:57:05 +00:00
// transformDecodeError adds additional information when a decode fails.
2016-05-09 15:54:43 +00:00
func transformDecodeError ( typer runtime . ObjectTyper , baseErr error , into runtime . Object , gvk * unversioned . GroupVersionKind , body [ ] byte ) error {
2015-12-21 05:15:35 +00:00
objGVK , err := typer . ObjectKind ( into )
2015-03-04 20:57:05 +00:00
if err != nil {
return err
}
2015-12-21 05:15:35 +00:00
if gvk != nil && len ( gvk . Kind ) > 0 {
return errors . NewBadRequest ( fmt . Sprintf ( "%s in version %q cannot be handled as a %s: %v" , gvk . Kind , gvk . Version , objGVK . Kind , baseErr ) )
2015-03-04 20:57:05 +00:00
}
2016-05-09 15:54:43 +00:00
summary := summarizeData ( body , 30 )
return errors . NewBadRequest ( fmt . Sprintf ( "the object provided is unrecognized (must be of type %s): %v (%s)" , objGVK . Kind , baseErr , summary ) )
2015-03-04 20:57:05 +00:00
}
2015-02-09 14:47:13 +00:00
// 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
2015-02-12 19:21:47 +00:00
func setSelfLink ( obj runtime . Object , req * restful . Request , namer ScopeNamer ) error {
// TODO: SelfLink generation should return a full URL?
path , query , err := namer . GenerateLink ( req , obj )
2015-02-09 14:47:13 +00:00
if err != nil {
2015-03-22 03:25:38 +00:00
return nil
2015-02-09 14:47:13 +00:00
}
2015-02-12 19:21:47 +00:00
newURL := * req . Request . URL
// use only canonical paths
newURL . Path = gpath . Clean ( path )
2015-02-09 14:47:13 +00:00
newURL . RawQuery = query
newURL . Fragment = ""
2015-02-12 19:21:47 +00:00
return namer . SetSelfLink ( obj , newURL . String ( ) )
}
2015-02-21 18:54:48 +00:00
// checkName checks the provided name against the request
func checkName ( obj runtime . Object , name , namespace string , namer ScopeNamer ) error {
if objNamespace , objName , err := namer . ObjectName ( obj ) ; err == nil {
2015-03-14 00:43:14 +00:00
if err != nil {
return err
}
2015-02-21 18:54:48 +00:00
if objName != name {
2015-03-14 00:43:14 +00:00
return errors . NewBadRequest ( fmt . Sprintf (
"the name of the object (%s) does not match the name on the URL (%s)" , objName , name ) )
2015-02-21 18:54:48 +00:00
}
if len ( namespace ) > 0 {
if len ( objNamespace ) > 0 && objNamespace != namespace {
2015-03-14 00:43:14 +00:00
return errors . NewBadRequest ( fmt . Sprintf (
"the namespace of the object (%s) does not match the namespace on the request (%s)" , objNamespace , namespace ) )
2015-02-21 18:54:48 +00:00
}
}
}
return nil
}
2015-02-12 19:21:47 +00:00
// setListSelfLink sets the self link of a list to the base URL, then sets the self links
2015-12-01 12:09:52 +00:00
// on all child objects returned. Returns the number of items in the list.
func setListSelfLink ( obj runtime . Object , req * restful . Request , namer ScopeNamer ) ( int , error ) {
2015-11-12 10:45:42 +00:00
if ! meta . IsListType ( obj ) {
2015-12-01 12:09:52 +00:00
return 0 , nil
2015-02-09 14:47:13 +00:00
}
2015-02-12 19:21:47 +00:00
// TODO: List SelfLink generation should return a full URL?
path , query , err := namer . GenerateListLink ( req )
if err != nil {
2015-12-01 12:09:52 +00:00
return 0 , err
2015-02-12 19:21:47 +00:00
}
newURL := * req . Request . URL
newURL . Path = path
newURL . RawQuery = query
// use the path that got us here
newURL . Fragment = ""
if err := namer . SetSelfLink ( obj , newURL . String ( ) ) ; err != nil {
glog . V ( 4 ) . Infof ( "Unable to set self link on object: %v" , err )
}
2015-02-09 14:47:13 +00:00
// Set self-link of objects in the list.
2015-11-12 10:45:42 +00:00
items , err := meta . ExtractList ( obj )
2015-02-09 14:47:13 +00:00
if err != nil {
2015-12-01 12:09:52 +00:00
return 0 , err
2015-02-09 14:47:13 +00:00
}
for i := range items {
2015-02-12 19:21:47 +00:00
if err := setSelfLink ( items [ i ] , req , namer ) ; err != nil {
2015-12-01 12:09:52 +00:00
return len ( items ) , err
2014-08-09 21:12:55 +00:00
}
}
2015-12-01 12:09:52 +00:00
return len ( items ) , meta . SetList ( obj , items )
2014-08-09 21:12:55 +00:00
}
2015-03-14 00:43:14 +00:00
2015-09-29 18:37:26 +00:00
func getPatchedJS ( patchType api . PatchType , originalJS , patchJS [ ] byte , obj runtime . Object ) ( [ ] byte , error ) {
2015-03-14 00:43:14 +00:00
switch patchType {
case api . JSONPatchType :
patchObj , err := jsonpatch . DecodePatch ( patchJS )
if err != nil {
return nil , err
}
return patchObj . Apply ( originalJS )
case api . MergePatchType :
return jsonpatch . MergePatch ( originalJS , patchJS )
case api . StrategicMergePatchType :
return strategicpatch . StrategicMergePatchData ( originalJS , patchJS , obj )
default :
// only here as a safety net - go-restful filters content-type
2015-09-29 18:37:26 +00:00
return nil , fmt . Errorf ( "unknown Content-Type header for patch: %v" , patchType )
2015-03-14 00:43:14 +00:00
}
}
2016-05-09 15:54:43 +00:00
func summarizeData ( data [ ] byte , maxLength int ) string {
switch {
case len ( data ) == 0 :
return "<empty>"
case data [ 0 ] == '{' :
if len ( data ) > maxLength {
return string ( data [ : maxLength ] ) + " ..."
}
return string ( data )
default :
if len ( data ) > maxLength {
return hex . EncodeToString ( data [ : maxLength ] ) + " ..."
}
return hex . EncodeToString ( data )
}
}