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 (
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-12-01 23:45:29 +00:00
"k8s.io/kubernetes/pkg/api/v1"
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"
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
runtime . Codec
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
}
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 {
2015-03-22 03:25:38 +00:00
errorJSON ( err , scope . Codec , w )
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 {
2015-03-22 03:25:38 +00:00
errorJSON ( err , scope . Codec , w )
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 {
errorJSON ( err , scope . Codec , w )
2015-02-09 14:47:13 +00:00
return
2014-12-09 19:23:21 +00:00
}
2015-11-30 16:29:19 +00:00
write ( http . StatusOK , scope . Kind . GroupVersion ( ) , scope . Codec , 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-01 23:45:29 +00:00
opts := v1 . ExportOptions { }
if err := scope . Codec . DecodeParametersInto ( req . Request . URL . Query ( ) , & opts ) ; err != nil {
return nil , err
}
internalOpts := unversioned . ExportOptions { }
scope . Convertor . Convert ( & opts , & internalOpts )
if internalOpts . Export {
if e == nil {
return nil , errors . NewBadRequest ( "export unsupported" )
}
return e . Export ( ctx , name , internalOpts )
}
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-01 23:45:29 +00:00
func GetResourceWithOptions ( r rest . GetterWithOptions , e rest . Exporter , scope RequestScope , internalKind , externalKind unversioned . GroupVersionKind , subpath bool , subpathKey string ) 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-11-30 16:29:19 +00:00
opts , err := getRequestOptions ( req , scope , internalKind , externalKind , subpath , subpathKey )
2015-04-06 16:58:00 +00:00
if err != nil {
return nil , err
}
2015-12-01 23:45:29 +00:00
exportOpts := unversioned . ExportOptions { }
if err := scope . Codec . DecodeParametersInto ( req . Request . URL . Query ( ) , & exportOpts ) ; err != nil {
return nil , err
}
if exportOpts . Export {
return nil , errors . NewBadRequest ( "export unsupported" )
}
2015-04-06 16:58:00 +00:00
return r . Get ( ctx , name , opts )
} )
}
2015-11-30 16:29:19 +00:00
func getRequestOptions ( req * restful . Request , scope RequestScope , internalKind , externalKind unversioned . GroupVersionKind , subpath bool , subpathKey string ) ( runtime . Object , error ) {
if internalKind . IsEmpty ( ) {
2015-04-14 14:57:00 +00:00
return nil , nil
}
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-11-18 14:26:29 +00:00
2015-12-08 19:40:23 +00:00
versioned , err := scope . Creater . New ( externalKind )
2015-11-10 09:20:57 +00:00
if err != nil {
2015-11-30 16:29:19 +00:00
return nil , err
2015-11-10 09:20:57 +00:00
}
2015-11-18 14:26:29 +00:00
2015-11-10 09:20:57 +00:00
if err := scope . Codec . DecodeParametersInto ( query , versioned ) ; err != nil {
return nil , errors . NewBadRequest ( err . Error ( ) )
}
2015-11-30 16:29:19 +00:00
out , err := scope . Convertor . ConvertToVersion ( versioned , internalKind . GroupVersion ( ) . String ( ) )
2015-11-10 09:20:57 +00:00
if err != nil {
// programmer error
return nil , err
}
return out , nil
2015-04-14 14:57:00 +00:00
}
// ConnectResource returns a function that handles a connect request on a rest.Storage object.
2015-11-30 16:29:19 +00:00
func ConnectResource ( connecter rest . Connecter , scope RequestScope , admit admission . Interface , internalKind , externalKind unversioned . GroupVersionKind , restPath string , subpath bool , subpathKey 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 {
errorJSON ( err , scope . Codec , w )
return
}
ctx := scope . ContextFunc ( req )
ctx = api . WithNamespace ( ctx , namespace )
2015-11-30 16:29:19 +00:00
opts , err := getRequestOptions ( req , scope , internalKind , externalKind , subpath , subpathKey )
2015-04-14 14:57:00 +00:00
if err != nil {
errorJSON ( err , scope . Codec , w )
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
2015-11-30 17:02:04 +00:00
err = admit . Admit ( admission . NewAttributesRecord ( connectRequest , scope . Kind . GroupKind ( ) , namespace , name , scope . Resource . GroupResource ( ) , scope . Subresource , admission . Connect , userInfo ) )
2015-05-08 19:02:12 +00:00
if err != nil {
errorJSON ( err , scope . Codec , w )
return
}
}
2015-10-11 03:14:18 +00:00
handler , err := connecter . Connect ( ctx , name , opts , & responder { scope : scope , req : req . Request , w : w } )
2015-04-14 14:57:00 +00:00
if err != nil {
errorJSON ( err , scope . Codec , w )
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
req * http . Request
w http . ResponseWriter
}
func ( r * responder ) Object ( statusCode int , obj runtime . Object ) {
2015-11-30 16:29:19 +00:00
write ( statusCode , r . scope . Kind . GroupVersion ( ) , r . scope . Codec , obj , r . w , r . req )
2015-10-11 03:14:18 +00:00
}
func ( r * responder ) Error ( err error ) {
errorJSON ( err , r . scope . Codec , r . w )
}
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 {
2015-03-22 03:25:38 +00:00
errorJSON ( err , scope . Codec , w )
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
listOptionsGVK := scope . Kind . GroupVersion ( ) . WithKind ( "ListOptions" )
versioned , err := scope . Creater . New ( listOptionsGVK )
if err != nil {
errorJSON ( err , scope . Codec , w )
return
}
if err := scope . Codec . DecodeParametersInto ( req . Request . URL . Query ( ) , versioned ) ; err != nil {
errorJSON ( err , scope . Codec , w )
return
}
opts := api . ListOptions { }
if err := scope . Convertor . Convert ( versioned , & opts ) ; err != nil {
2015-11-10 09:20:57 +00:00
errorJSON ( err , scope . Codec , w )
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 ( ) )
errorJSON ( err , scope . Codec , w )
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.
errorJSON (
errors . NewBadRequest ( "both a name and a field selector provided; please provide one or the other." ) ,
scope . Codec ,
w ,
)
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 {
errorJSON ( err , scope . Codec , w )
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 ) )
}
serveWatch ( watcher , scope , w , req , 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 {
2015-03-22 03:25:38 +00:00
errorJSON ( err , scope . Codec , w )
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 {
2015-03-22 03:25:38 +00:00
errorJSON ( err , scope . Codec , w )
2015-02-09 14:47:13 +00:00
return
}
2015-12-01 12:09:52 +00:00
trace . Step ( "Self-linking done" )
2015-11-30 16:29:19 +00:00
write ( http . StatusOK , scope . Kind . GroupVersion ( ) , scope . Codec , 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 {
2015-03-22 03:25:38 +00:00
errorJSON ( err , scope . Codec , w )
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-02-09 14:47:13 +00:00
body , err := readBody ( req . Request )
if err != nil {
2015-03-22 03:25:38 +00:00
errorJSON ( err , scope . Codec , w )
2015-02-09 14:47:13 +00:00
return
2014-08-09 21:12:55 +00:00
}
2015-02-09 14:47:13 +00:00
obj := r . New ( )
2015-12-03 11:31:22 +00:00
trace . Step ( "About to convert to expected version" )
2015-11-17 15:07:45 +00:00
// TODO this cleans up with proper typing
2015-11-30 16:29:19 +00:00
if err := scope . Codec . DecodeIntoWithSpecifiedVersionKind ( body , obj , scope . Kind ) ; err != nil {
2015-03-04 20:57:05 +00:00
err = transformDecodeError ( typer , err , obj , body )
2015-03-22 03:25:38 +00:00
errorJSON ( err , scope . Codec , w )
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
2015-11-30 17:02:04 +00:00
err = admit . Admit ( admission . NewAttributesRecord ( obj , scope . Kind . GroupKind ( ) , namespace , name , scope . Resource . GroupResource ( ) , scope . Subresource , admission . Create , userInfo ) )
2015-05-15 14:48:33 +00:00
if err != nil {
errorJSON ( err , scope . Codec , w )
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 {
2015-03-22 03:25:38 +00:00
errorJSON ( err , scope . Codec , w )
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 {
errorJSON ( err , scope . Codec , w )
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-11-30 16:29:19 +00:00
write ( http . StatusCreated , scope . Kind . GroupVersion ( ) , scope . Codec , 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 {
2015-03-22 03:25:38 +00:00
errorJSON ( err , scope . Codec , w )
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 )
2015-11-30 16:29:19 +00:00
versionedObj , err := converter . ConvertToVersion ( r . New ( ) , scope . Kind . GroupVersion ( ) . String ( ) )
2015-03-14 00:43:14 +00:00
if err != nil {
errorJSON ( err , scope . Codec , w )
return
}
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 {
2015-03-22 03:25:38 +00:00
errorJSON ( err , scope . Codec , w )
2015-02-21 18:54:48 +00:00
return
}
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 )
return admit . Admit ( admission . NewAttributesRecord ( updatedObject , scope . Kind . GroupKind ( ) , namespace , name , scope . Resource . GroupResource ( ) , scope . Subresource , admission . Update , userInfo ) )
}
return nil
}
result , err := patchResource ( ctx , updateAdmit , timeout , versionedObj , r , name , patchType , patchJS , scope . Namer , scope . Codec )
2015-02-21 18:54:48 +00:00
if err != nil {
2015-03-22 03:25:38 +00:00
errorJSON ( err , scope . Codec , w )
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 {
errorJSON ( err , scope . Codec , w )
2015-02-21 18:54:48 +00:00
return
}
2015-11-30 16:29:19 +00:00
write ( http . StatusOK , scope . Kind . GroupVersion ( ) , scope . Codec , 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
}
currentPatch , err := strategicpatch . CreateStrategicMergePatch ( originalObjJS , currentObjectJS , patcher . New ( ) )
if err != nil {
return nil , err
}
originalPatch , err := strategicpatch . CreateStrategicMergePatch ( originalObjJS , originalPatchedObjJS , patcher . New ( ) )
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 {
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 {
2015-03-22 03:25:38 +00:00
errorJSON ( err , scope . Codec , w )
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 {
2015-03-22 03:25:38 +00:00
errorJSON ( err , scope . Codec , w )
2015-02-09 14:47:13 +00:00
return
2014-08-09 21:12:55 +00:00
}
2015-02-09 14:47:13 +00:00
obj := r . New ( )
2015-12-03 11:31:22 +00:00
trace . Step ( "About to convert to expected version" )
2015-11-30 16:29:19 +00:00
if err := scope . Codec . DecodeIntoWithSpecifiedVersionKind ( body , obj , scope . Kind ) ; err != nil {
2015-03-04 20:57:05 +00:00
err = transformDecodeError ( typer , err , obj , body )
2015-03-22 03:25:38 +00:00
errorJSON ( err , scope . Codec , w )
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 {
errorJSON ( err , scope . Codec , w )
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
2015-11-30 17:02:04 +00:00
err = admit . Admit ( admission . NewAttributesRecord ( obj , scope . Kind . GroupKind ( ) , namespace , name , scope . Resource . GroupResource ( ) , scope . Subresource , admission . Update , userInfo ) )
2015-05-15 14:48:33 +00:00
if err != nil {
errorJSON ( err , scope . Codec , w )
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 {
2015-03-22 03:25:38 +00:00
errorJSON ( err , scope . Codec , w )
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 {
errorJSON ( err , scope . Codec , w )
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-06-08 23:33:58 +00:00
writeJSON ( status , scope . Codec , result , w , isPrettyPrint ( 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 {
2015-03-22 03:25:38 +00:00
errorJSON ( err , scope . Codec , w )
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 {
2015-03-22 03:25:38 +00:00
errorJSON ( err , scope . Codec , w )
2015-03-05 03:34:31 +00:00
return
}
if len ( body ) > 0 {
2015-03-22 03:25:38 +00:00
if err := scope . Codec . DecodeInto ( body , options ) ; err != nil {
errorJSON ( err , scope . Codec , w )
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
2015-11-30 17:02:04 +00:00
err = admit . Admit ( admission . NewAttributesRecord ( nil , scope . Kind . GroupKind ( ) , namespace , name , scope . Resource . GroupResource ( ) , scope . Subresource , admission . Delete , userInfo ) )
2015-05-15 14:48:33 +00:00
if err != nil {
errorJSON ( err , scope . Codec , w )
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 {
2015-03-22 03:25:38 +00:00
errorJSON ( err , scope . Codec , w )
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 {
errorJSON ( err , scope . Codec , w )
2015-12-07 09:21:42 +00:00
return
}
}
}
write ( http . StatusOK , scope . Kind . GroupVersion ( ) , scope . Codec , result , w , req . Request )
}
}
// 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 {
errorJSON ( err , scope . Codec , w )
return
}
ctx := scope . ContextFunc ( req )
ctx = api . WithNamespace ( ctx , namespace )
if admit != nil && admit . Handles ( admission . Delete ) {
userInfo , _ := api . UserFrom ( ctx )
err = admit . Admit ( admission . NewAttributesRecord ( nil , scope . Kind . GroupKind ( ) , namespace , "" , scope . Resource . GroupResource ( ) , scope . Subresource , admission . Delete , userInfo ) )
if err != nil {
errorJSON ( err , scope . Codec , w )
return
}
}
2015-12-16 13:02:09 +00:00
listOptionsGVK := scope . Kind . GroupVersion ( ) . WithKind ( "ListOptions" )
versioned , err := scope . Creater . New ( listOptionsGVK )
if err != nil {
errorJSON ( err , scope . Codec , w )
return
}
if err := scope . Codec . DecodeParametersInto ( req . Request . URL . Query ( ) , versioned ) ; err != nil {
errorJSON ( err , scope . Codec , w )
return
}
listOptions := api . ListOptions { }
if err := scope . Convertor . Convert ( versioned , & listOptions ) ; err != nil {
2015-12-07 09:21:42 +00:00
errorJSON ( err , scope . Codec , w )
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 ( ) )
errorJSON ( err , scope . Codec , w )
return
}
}
options := & api . DeleteOptions { }
if checkBody {
body , err := readBody ( req . Request )
if err != nil {
errorJSON ( err , scope . Codec , w )
return
}
if len ( body ) > 0 {
if err := scope . Codec . DecodeInto ( body , options ) ; err != nil {
errorJSON ( err , scope . Codec , w )
return
}
}
}
result , err := finishRequest ( timeout , func ( ) ( runtime . Object , error ) {
return r . DeleteCollection ( ctx , options , & listOptions )
} )
if err != nil {
errorJSON ( err , scope . Codec , w )
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 {
errorJSON ( err , scope . Codec , w )
2015-02-10 14:26:26 +00:00
return
}
}
2015-02-09 14:47:13 +00:00
}
2015-11-30 16:29:19 +00:00
write ( http . StatusOK , scope . Kind . GroupVersion ( ) , scope . Codec , result , w , req . Request )
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
defer util . HandleCrash ( func ( panicReason interface { } ) {
// 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.
func transformDecodeError ( typer runtime . ObjectTyper , baseErr error , into runtime . Object , body [ ] byte ) error {
2015-11-20 12:38:32 +00:00
objectGroupVersionKind , err := typer . ObjectKind ( into )
2015-03-04 20:57:05 +00:00
if err != nil {
return err
}
2015-11-20 12:38:32 +00:00
if dataGroupVersionKind , err := typer . DataKind ( body ) ; err == nil && len ( dataGroupVersionKind . Kind ) > 0 {
return errors . NewBadRequest ( fmt . Sprintf ( "%s in version %v cannot be handled as a %s: %v" , dataGroupVersionKind . Kind , dataGroupVersionKind . GroupVersion ( ) , objectGroupVersionKind . Kind , baseErr ) )
2015-03-04 20:57:05 +00:00
}
2015-11-20 12:38:32 +00:00
return errors . NewBadRequest ( fmt . Sprintf ( "the object provided is unrecognized (must be of type %s): %v" , objectGroupVersionKind . Kind , baseErr ) )
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
}
}