2015-01-31 00:08:59 +00:00
/ *
2015-05-01 16:19:44 +00:00
Copyright 2015 The Kubernetes Authors All rights reserved .
2015-01-31 00:08:59 +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-02-09 14:47:13 +00:00
"fmt"
2015-01-31 00:08:59 +00:00
"net/http"
2015-02-09 14:47:13 +00:00
gpath "path"
2015-01-31 00:08:59 +00:00
"reflect"
2015-03-13 05:44:45 +00:00
"sort"
2015-02-09 14:47:13 +00:00
"strings"
2015-06-16 02:39:31 +00:00
"time"
2015-01-31 00:08:59 +00:00
2015-08-05 22:03:47 +00:00
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/errors"
"k8s.io/kubernetes/pkg/api/meta"
"k8s.io/kubernetes/pkg/api/rest"
2015-10-09 01:33:33 +00:00
"k8s.io/kubernetes/pkg/api/unversioned"
2015-12-08 14:21:04 +00:00
"k8s.io/kubernetes/pkg/apis/extensions"
2016-01-12 19:54:48 +00:00
"k8s.io/kubernetes/pkg/apiserver/metrics"
2015-08-05 22:03:47 +00:00
"k8s.io/kubernetes/pkg/conversion"
"k8s.io/kubernetes/pkg/runtime"
2015-01-31 00:08:59 +00:00
"github.com/emicklei/go-restful"
)
type APIInstaller struct {
2015-05-27 01:39:42 +00:00
group * APIGroupVersion
2015-10-20 17:34:26 +00:00
info * RequestInfoResolver
2015-05-27 01:39:42 +00:00
prefix string // Path prefix where API resources are to be registered.
2015-06-16 02:39:31 +00:00
minRequestTimeout time . Duration
2015-01-31 00:08:59 +00:00
}
// Struct capturing information about an action ("GET", "POST", "WATCH", PROXY", etc).
type action struct {
Verb string // Verb identifying the action ("GET", "POST", "WATCH", PROXY", etc).
Path string // The path of the action
Params [ ] * restful . Parameter // List of parameters associated with the action.
2015-02-12 19:21:47 +00:00
Namer ScopeNamer
2015-01-31 00:08:59 +00:00
}
2015-07-25 01:33:03 +00:00
// An interface to see if an object supports swagger documentation as a method
type documentable interface {
SwaggerDoc ( ) map [ string ] string
}
2015-02-12 14:45:13 +00:00
// errEmptyName is returned when API requests do not fill the name section of the path.
2015-03-17 23:03:38 +00:00
var errEmptyName = errors . NewBadRequest ( "name must be provided" )
2015-02-12 14:45:13 +00:00
2015-01-31 00:08:59 +00:00
// Installs handlers for API resources.
2015-10-09 01:33:22 +00:00
func ( a * APIInstaller ) Install ( ws * restful . WebService ) ( apiResources [ ] unversioned . APIResource , errors [ ] error ) {
2015-09-15 03:55:18 +00:00
errors = make ( [ ] error , 0 )
2015-01-31 00:08:59 +00:00
2015-12-21 05:15:35 +00:00
proxyHandler := ( & ProxyHandler {
prefix : a . prefix + "/proxy/" ,
storage : a . group . Storage ,
serializer : a . group . Serializer ,
context : a . group . Context ,
requestInfoResolver : a . info ,
} )
2015-01-31 00:08:59 +00:00
2015-03-13 05:44:45 +00:00
// Register the paths in a deterministic (sorted) order to get a deterministic swagger spec.
paths := make ( [ ] string , len ( a . group . Storage ) )
var i int = 0
for path := range a . group . Storage {
paths [ i ] = path
i ++
}
sort . Strings ( paths )
for _ , path := range paths {
2015-09-15 03:55:18 +00:00
apiResource , err := a . registerResourceHandlers ( path , a . group . Storage [ path ] , ws , proxyHandler )
if err != nil {
2015-12-22 21:22:28 +00:00
errors = append ( errors , fmt . Errorf ( "error in registering resource: %s, %v" , path , err ) )
2015-01-31 00:08:59 +00:00
}
2015-09-15 03:55:18 +00:00
if apiResource != nil {
apiResources = append ( apiResources , * apiResource )
}
2015-01-31 00:08:59 +00:00
}
2015-09-15 03:55:18 +00:00
return apiResources , errors
2015-01-31 00:08:59 +00:00
}
2015-09-14 20:34:11 +00:00
// NewWebService creates a new restful webservice with the api installer's prefix and version.
func ( a * APIInstaller ) NewWebService ( ) * restful . WebService {
2015-01-31 00:08:59 +00:00
ws := new ( restful . WebService )
ws . Path ( a . prefix )
2015-09-28 18:08:47 +00:00
// a.prefix contains "prefix/group/version"
ws . Doc ( "API at " + a . prefix )
2016-02-12 19:33:32 +00:00
// Backwards compatibility, we accepted objects with empty content-type at V1.
2015-12-21 05:15:35 +00:00
// If we stop using go-restful, we can default empty content-type to application/json on an
// endpoint by endpoint basis
2015-01-31 00:08:59 +00:00
ws . Consumes ( "*/*" )
2015-12-21 05:15:35 +00:00
ws . Produces ( a . group . Serializer . SupportedMediaTypes ( ) ... )
2015-11-12 20:20:20 +00:00
ws . ApiVersion ( a . group . GroupVersion . String ( ) )
2015-04-09 02:47:31 +00:00
2015-01-31 00:08:59 +00:00
return ws
}
2016-02-24 19:10:37 +00:00
// getResourceKind returns the external group version kind registered for the given storage
// object. If the storage object is a subresource and has an override supplied for it, it returns
// the group version kind supplied in the override.
func ( a * APIInstaller ) getResourceKind ( path string , storage rest . Storage ) ( unversioned . GroupVersionKind , error ) {
if fqKindToRegister , ok := a . group . SubresourceGroupVersionKind [ path ] ; ok {
return fqKindToRegister , nil
2015-02-24 05:42:27 +00:00
}
2015-02-09 14:47:13 +00:00
2015-01-31 00:08:59 +00:00
object := storage . New ( )
2015-11-20 12:38:32 +00:00
fqKinds , err := a . group . Typer . ObjectKinds ( object )
2015-01-31 00:08:59 +00:00
if err != nil {
2016-02-24 19:10:37 +00:00
return unversioned . GroupVersionKind { } , err
2015-01-31 00:08:59 +00:00
}
2016-02-24 19:10:37 +00:00
2015-11-20 12:38:32 +00:00
// a given go type can have multiple potential fully qualified kinds. Find the one that corresponds with the group
// we're trying to register here
fqKindToRegister := unversioned . GroupVersionKind { }
for _ , fqKind := range fqKinds {
if fqKind . Group == a . group . GroupVersion . Group {
2016-02-24 19:10:37 +00:00
fqKindToRegister = a . group . GroupVersion . WithKind ( fqKind . Kind )
2015-11-20 12:38:32 +00:00
break
}
// TODO This keeps it doing what it was doing before, but it doesn't feel right.
2015-12-08 14:21:04 +00:00
if fqKind . Group == extensions . GroupName && fqKind . Kind == "ThirdPartyResourceData" {
2016-02-24 19:10:37 +00:00
fqKindToRegister = a . group . GroupVersion . WithKind ( fqKind . Kind )
2015-11-20 12:38:32 +00:00
}
}
2016-02-24 19:10:37 +00:00
if fqKindToRegister . IsEmpty ( ) {
return unversioned . GroupVersionKind { } , fmt . Errorf ( "unable to locate fully qualified kind for %v: found %v when registering for %v" , reflect . TypeOf ( object ) , fqKinds , a . group . GroupVersion )
}
return fqKindToRegister , nil
}
2015-11-20 12:38:32 +00:00
2016-02-24 19:10:37 +00:00
// restMapping returns rest mapper for the resource.
// Example REST paths that this mapper maps.
// 1. Resource only, no subresource:
// Resource Type: batch/v1.Job (input args: resource = "jobs")
// REST path: /apis/batch/v1/namespaces/{namespace}/job/{name}
// 2. Subresource and its parent belong to different API groups and/or versions:
// Resource Type: extensions/v1beta1.ReplicaSet (input args: resource = "replicasets")
// Subresource Type: autoscaling/v1.Scale
// REST path: /apis/extensions/v1beta1/namespaces/{namespace}/replicaset/{name}/scale
func ( a * APIInstaller ) restMapping ( resource string ) ( * meta . RESTMapping , error ) {
// subresources must have parent resources, and follow the namespacing rules of their parent.
// So get the storage of the resource (which is the parent resource in case of subresources)
storage , ok := a . group . Storage [ resource ]
if ! ok {
return nil , fmt . Errorf ( "unable to locate the storage object for resource: %s" , resource )
}
fqKindToRegister , err := a . getResourceKind ( resource , storage )
if err != nil {
return nil , fmt . Errorf ( "unable to locate fully qualified kind for mapper resource %s: %v" , resource , err )
}
return a . group . Mapper . RESTMapping ( fqKindToRegister . GroupKind ( ) , fqKindToRegister . Version )
}
2015-12-22 18:57:12 +00:00
2016-02-24 19:10:37 +00:00
func ( a * APIInstaller ) registerResourceHandlers ( path string , storage rest . Storage , ws * restful . WebService , proxyHandler http . Handler ) ( * unversioned . APIResource , error ) {
admit := a . group . Admit
context := a . group . Context
optionsExternalVersion := a . group . GroupVersion
if a . group . OptionsExternalVersion != nil {
optionsExternalVersion = * a . group . OptionsExternalVersion
2015-11-20 12:38:32 +00:00
}
2015-11-16 21:24:59 +00:00
2016-02-24 19:10:37 +00:00
resource , subresource , err := splitSubresource ( path )
2015-01-31 00:08:59 +00:00
if err != nil {
2015-09-15 03:55:18 +00:00
return nil , err
2015-01-31 00:08:59 +00:00
}
2016-02-24 19:10:37 +00:00
mapping , err := a . restMapping ( resource )
2015-01-31 00:08:59 +00:00
if err != nil {
2015-09-15 03:55:18 +00:00
return nil , err
2015-01-31 00:08:59 +00:00
}
2016-02-24 19:10:37 +00:00
fqKindToRegister , err := a . getResourceKind ( path , storage )
if err != nil {
return nil , err
}
2015-11-16 21:24:59 +00:00
2016-02-24 19:10:37 +00:00
versionedPtr , err := a . group . Creater . New ( fqKindToRegister )
if err != nil {
return nil , err
2015-05-08 04:31:53 +00:00
}
2016-02-24 19:10:37 +00:00
versionedObject := indirectArbitraryPointer ( versionedPtr )
kind := fqKindToRegister . Kind
hasSubresource := len ( subresource ) > 0
2015-05-08 04:31:53 +00:00
2015-01-31 00:08:59 +00:00
// what verbs are supported by the storage, used to know what verbs we support per path
2015-03-21 16:24:16 +00:00
creater , isCreater := storage . ( rest . Creater )
2015-05-04 18:38:41 +00:00
namedCreater , isNamedCreater := storage . ( rest . NamedCreater )
2015-03-21 16:24:16 +00:00
lister , isLister := storage . ( rest . Lister )
getter , isGetter := storage . ( rest . Getter )
2015-04-06 16:58:00 +00:00
getterWithOptions , isGetterWithOptions := storage . ( rest . GetterWithOptions )
2015-03-21 16:24:16 +00:00
deleter , isDeleter := storage . ( rest . Deleter )
gracefulDeleter , isGracefulDeleter := storage . ( rest . GracefulDeleter )
2015-12-07 09:21:42 +00:00
collectionDeleter , isCollectionDeleter := storage . ( rest . CollectionDeleter )
2015-03-21 16:24:16 +00:00
updater , isUpdater := storage . ( rest . Updater )
patcher , isPatcher := storage . ( rest . Patcher )
2015-03-24 04:07:22 +00:00
watcher , isWatcher := storage . ( rest . Watcher )
2015-03-21 16:24:16 +00:00
_ , isRedirector := storage . ( rest . Redirector )
2015-04-14 14:57:00 +00:00
connecter , isConnecter := storage . ( rest . Connecter )
2015-03-22 03:25:38 +00:00
storageMeta , isMetadata := storage . ( rest . StorageMetadata )
if ! isMetadata {
storageMeta = defaultStorageMetadata { }
}
2015-12-01 23:45:29 +00:00
exporter , isExporter := storage . ( rest . Exporter )
if ! isExporter {
exporter = nil
}
versionedExportOptions , err := a . group . Creater . New ( optionsExternalVersion . WithKind ( "ExportOptions" ) )
if err != nil {
return nil , err
}
2015-01-31 00:08:59 +00:00
2015-05-04 18:38:41 +00:00
if isNamedCreater {
isCreater = true
}
2015-03-24 04:07:22 +00:00
var versionedList interface { }
if isLister {
list := lister . NewList ( )
2015-11-20 12:38:32 +00:00
listGVK , err := a . group . Typer . ObjectKind ( list )
2015-12-08 19:40:23 +00:00
versionedListPtr , err := a . group . Creater . New ( a . group . GroupVersion . WithKind ( listGVK . Kind ) )
2015-03-24 04:07:22 +00:00
if err != nil {
2015-09-15 03:55:18 +00:00
return nil , err
2015-03-24 04:07:22 +00:00
}
versionedList = indirectArbitraryPointer ( versionedListPtr )
}
2015-12-08 19:40:23 +00:00
versionedListOptions , err := a . group . Creater . New ( optionsExternalVersion . WithKind ( "ListOptions" ) )
2015-03-22 21:43:00 +00:00
if err != nil {
2015-09-15 03:55:18 +00:00
return nil , err
2015-03-22 21:43:00 +00:00
}
2015-04-09 02:47:31 +00:00
var versionedDeleterObject interface { }
2015-03-05 03:34:31 +00:00
switch {
case isGracefulDeleter :
2015-12-08 19:40:23 +00:00
objectPtr , err := a . group . Creater . New ( optionsExternalVersion . WithKind ( "DeleteOptions" ) )
2015-03-05 03:34:31 +00:00
if err != nil {
2015-09-15 03:55:18 +00:00
return nil , err
2015-03-05 03:34:31 +00:00
}
2015-04-09 02:47:31 +00:00
versionedDeleterObject = indirectArbitraryPointer ( objectPtr )
2015-03-05 03:34:31 +00:00
isDeleter = true
case isDeleter :
2015-08-08 01:52:23 +00:00
gracefulDeleter = rest . GracefulDeleteAdapter { Deleter : deleter }
2015-03-05 03:34:31 +00:00
}
2015-12-08 19:40:23 +00:00
versionedStatusPtr , err := a . group . Creater . New ( optionsExternalVersion . WithKind ( "Status" ) )
2015-04-09 02:47:31 +00:00
if err != nil {
2015-09-15 03:55:18 +00:00
return nil , err
2015-04-09 02:47:31 +00:00
}
versionedStatus := indirectArbitraryPointer ( versionedStatusPtr )
2015-04-11 15:13:10 +00:00
var (
2015-11-30 16:29:19 +00:00
getOptions runtime . Object
versionedGetOptions runtime . Object
getOptionsInternalKind unversioned . GroupVersionKind
getSubpath bool
2015-04-11 15:13:10 +00:00
)
2015-04-06 16:58:00 +00:00
if isGetterWithOptions {
2015-12-21 05:15:35 +00:00
getOptions , getSubpath , _ = getterWithOptions . NewGetOptions ( )
2015-11-20 12:38:32 +00:00
getOptionsInternalKind , err = a . group . Typer . ObjectKind ( getOptions )
2015-11-30 16:29:19 +00:00
if err != nil {
return nil , err
}
2015-12-08 19:40:23 +00:00
versionedGetOptions , err = a . group . Creater . New ( optionsExternalVersion . WithKind ( getOptionsInternalKind . Kind ) )
2015-06-26 21:10:28 +00:00
if err != nil {
2015-09-15 03:55:18 +00:00
return nil , err
2015-06-26 21:10:28 +00:00
}
2015-04-06 16:58:00 +00:00
isGetter = true
}
2016-04-05 03:07:43 +00:00
var versionedWatchEvent runtime . Object
if isWatcher {
versionedWatchEvent , err = a . group . Creater . New ( a . group . GroupVersion . WithKind ( "WatchEvent" ) )
if err != nil {
return nil , err
}
}
2015-04-14 14:57:00 +00:00
var (
2015-11-30 16:29:19 +00:00
connectOptions runtime . Object
versionedConnectOptions runtime . Object
connectOptionsInternalKind unversioned . GroupVersionKind
connectSubpath bool
2015-04-14 14:57:00 +00:00
)
if isConnecter {
2015-12-21 05:15:35 +00:00
connectOptions , connectSubpath , _ = connecter . NewConnectOptions ( )
2015-04-14 14:57:00 +00:00
if connectOptions != nil {
2015-11-20 12:38:32 +00:00
connectOptionsInternalKind , err = a . group . Typer . ObjectKind ( connectOptions )
2015-11-30 16:29:19 +00:00
if err != nil {
return nil , err
}
2015-12-08 19:40:23 +00:00
versionedConnectOptions , err = a . group . Creater . New ( optionsExternalVersion . WithKind ( connectOptionsInternalKind . Kind ) )
2015-04-14 14:57:00 +00:00
}
}
2015-02-11 22:09:25 +00:00
var ctxFn ContextFunc
ctxFn = func ( req * restful . Request ) api . Context {
2015-08-20 05:08:26 +00:00
if context == nil {
return api . NewContext ( )
}
2015-02-11 22:09:25 +00:00
if ctx , ok := context . Get ( req . Request ) ; ok {
return ctx
}
return api . NewContext ( )
}
2015-02-09 14:47:13 +00:00
2015-02-19 20:37:50 +00:00
allowWatchList := isWatcher && isLister // watching on lists is allowed only for kinds that support both watch and list.
2015-01-31 00:08:59 +00:00
scope := mapping . Scope
nameParam := ws . PathParameter ( "name" , "name of the " + kind ) . DataType ( "string" )
2015-09-16 03:36:51 +00:00
pathParam := ws . PathParameter ( "path" , "path to the resource" ) . DataType ( "string" )
2015-12-01 23:45:29 +00:00
2015-01-31 00:08:59 +00:00
params := [ ] * restful . Parameter { }
actions := [ ] action { }
2015-02-12 19:21:47 +00:00
2015-12-22 18:57:12 +00:00
var resourceKind string
kindProvider , ok := storage . ( rest . KindProvider )
if ok {
resourceKind = kindProvider . Kind ( )
} else {
2016-02-24 19:10:37 +00:00
resourceKind = kind
2015-12-22 18:57:12 +00:00
}
2015-10-09 01:33:22 +00:00
var apiResource unversioned . APIResource
2015-01-31 00:08:59 +00:00
// Get the list of actions for the given scope.
2015-06-18 20:20:28 +00:00
switch scope . Name ( ) {
case meta . RESTScopeNameRoot :
2015-06-18 00:06:12 +00:00
// Handle non-namespace scoped resources like nodes.
2015-03-04 20:57:05 +00:00
resourcePath := resource
2015-04-11 15:13:10 +00:00
resourceParams := params
2015-03-04 20:57:05 +00:00
itemPath := resourcePath + "/{name}"
2015-04-11 15:13:10 +00:00
nameParams := append ( params , nameParam )
proxyParams := append ( nameParams , pathParam )
if hasSubresource {
2015-02-24 05:42:27 +00:00
itemPath = itemPath + "/" + subresource
2015-03-04 20:57:05 +00:00
resourcePath = itemPath
2015-04-11 15:13:10 +00:00
resourceParams = nameParams
2015-02-24 05:42:27 +00:00
}
2015-09-15 03:55:18 +00:00
apiResource . Name = path
apiResource . Namespaced = false
2015-12-22 18:57:12 +00:00
apiResource . Kind = resourceKind
2015-03-04 20:57:05 +00:00
namer := rootScopeNaming { scope , a . group . Linker , gpath . Join ( a . prefix , itemPath ) }
2015-02-09 14:47:13 +00:00
2015-02-12 19:21:47 +00:00
// Handler for standard REST verbs (GET, PUT, POST and DELETE).
2015-06-18 00:06:12 +00:00
// Add actions at the resource path: /api/apiVersion/resource
2015-04-11 15:13:10 +00:00
actions = appendIf ( actions , action { "LIST" , resourcePath , resourceParams , namer } , isLister )
actions = appendIf ( actions , action { "POST" , resourcePath , resourceParams , namer } , isCreater )
2015-12-07 09:21:42 +00:00
actions = appendIf ( actions , action { "DELETECOLLECTION" , resourcePath , resourceParams , namer } , isCollectionDeleter )
// DEPRECATED
2015-04-11 15:13:10 +00:00
actions = appendIf ( actions , action { "WATCHLIST" , "watch/" + resourcePath , resourceParams , namer } , allowWatchList )
2015-02-12 19:21:47 +00:00
2015-06-18 00:06:12 +00:00
// Add actions at the item path: /api/apiVersion/resource/{name}
2015-02-19 20:37:50 +00:00
actions = appendIf ( actions , action { "GET" , itemPath , nameParams , namer } , isGetter )
2015-04-11 15:13:10 +00:00
if getSubpath {
actions = appendIf ( actions , action { "GET" , itemPath + "/{path:*}" , proxyParams , namer } , isGetter )
}
2015-02-19 20:37:50 +00:00
actions = appendIf ( actions , action { "PUT" , itemPath , nameParams , namer } , isUpdater )
2015-02-21 18:54:48 +00:00
actions = appendIf ( actions , action { "PATCH" , itemPath , nameParams , namer } , isPatcher )
2015-02-19 20:37:50 +00:00
actions = appendIf ( actions , action { "DELETE" , itemPath , nameParams , namer } , isDeleter )
2015-03-04 20:57:05 +00:00
actions = appendIf ( actions , action { "WATCH" , "watch/" + itemPath , nameParams , namer } , isWatcher )
2015-12-03 06:57:26 +00:00
// We add "proxy" subresource to remove the need for the generic top level prefix proxy.
// The generic top level prefix proxy is deprecated in v1.2, and will be removed in 1.3, or 1.4 at the latest.
// TODO: DEPRECATED in v1.2.
2015-03-27 23:16:56 +00:00
actions = appendIf ( actions , action { "PROXY" , "proxy/" + itemPath + "/{path:*}" , proxyParams , namer } , isRedirector )
2015-12-03 06:57:26 +00:00
// TODO: DEPRECATED in v1.2.
2015-03-04 20:57:05 +00:00
actions = appendIf ( actions , action { "PROXY" , "proxy/" + itemPath , nameParams , namer } , isRedirector )
2015-04-14 14:57:00 +00:00
actions = appendIf ( actions , action { "CONNECT" , itemPath , nameParams , namer } , isConnecter )
2015-06-17 20:22:44 +00:00
actions = appendIf ( actions , action { "CONNECT" , itemPath + "/{path:*}" , proxyParams , namer } , isConnecter && connectSubpath )
2015-06-18 20:20:28 +00:00
break
case meta . RESTScopeNameNamespace :
// Handler for standard REST verbs (GET, PUT, POST and DELETE).
namespaceParam := ws . PathParameter ( scope . ArgumentName ( ) , scope . ParamDescription ( ) ) . DataType ( "string" )
namespacedPath := scope . ParamName ( ) + "/{" + scope . ArgumentName ( ) + "}/" + resource
namespaceParams := [ ] * restful . Parameter { namespaceParam }
resourcePath := namespacedPath
resourceParams := namespaceParams
itemPath := namespacedPath + "/{name}"
nameParams := append ( namespaceParams , nameParam )
proxyParams := append ( nameParams , pathParam )
if hasSubresource {
itemPath = itemPath + "/" + subresource
resourcePath = itemPath
resourceParams = nameParams
}
2015-09-15 03:55:18 +00:00
apiResource . Name = path
apiResource . Namespaced = true
2015-12-22 18:57:12 +00:00
apiResource . Kind = resourceKind
2015-06-18 20:20:28 +00:00
namer := scopeNaming { scope , a . group . Linker , gpath . Join ( a . prefix , itemPath ) , false }
2015-02-09 14:47:13 +00:00
2015-06-18 20:20:28 +00:00
actions = appendIf ( actions , action { "LIST" , resourcePath , resourceParams , namer } , isLister )
actions = appendIf ( actions , action { "POST" , resourcePath , resourceParams , namer } , isCreater )
2015-12-07 09:21:42 +00:00
actions = appendIf ( actions , action { "DELETECOLLECTION" , resourcePath , resourceParams , namer } , isCollectionDeleter )
2015-06-18 20:20:28 +00:00
// DEPRECATED
actions = appendIf ( actions , action { "WATCHLIST" , "watch/" + resourcePath , resourceParams , namer } , allowWatchList )
actions = appendIf ( actions , action { "GET" , itemPath , nameParams , namer } , isGetter )
if getSubpath {
actions = appendIf ( actions , action { "GET" , itemPath + "/{path:*}" , proxyParams , namer } , isGetter )
}
actions = appendIf ( actions , action { "PUT" , itemPath , nameParams , namer } , isUpdater )
actions = appendIf ( actions , action { "PATCH" , itemPath , nameParams , namer } , isPatcher )
actions = appendIf ( actions , action { "DELETE" , itemPath , nameParams , namer } , isDeleter )
actions = appendIf ( actions , action { "WATCH" , "watch/" + itemPath , nameParams , namer } , isWatcher )
2015-12-03 06:57:26 +00:00
// We add "proxy" subresource to remove the need for the generic top level prefix proxy.
// The generic top level prefix proxy is deprecated in v1.2, and will be removed in 1.3, or 1.4 at the latest.
// TODO: DEPRECATED in v1.2.
2015-06-18 20:20:28 +00:00
actions = appendIf ( actions , action { "PROXY" , "proxy/" + itemPath + "/{path:*}" , proxyParams , namer } , isRedirector )
2015-12-03 06:57:26 +00:00
// TODO: DEPRECATED in v1.2.
2015-06-18 20:20:28 +00:00
actions = appendIf ( actions , action { "PROXY" , "proxy/" + itemPath , nameParams , namer } , isRedirector )
actions = appendIf ( actions , action { "CONNECT" , itemPath , nameParams , namer } , isConnecter )
actions = appendIf ( actions , action { "CONNECT" , itemPath + "/{path:*}" , proxyParams , namer } , isConnecter && connectSubpath )
// list or post across namespace.
// For ex: LIST all pods in all namespaces by sending a LIST request at /api/apiVersion/pods.
// TODO: more strongly type whether a resource allows these actions on "all namespaces" (bulk delete)
if ! hasSubresource {
namer = scopeNaming { scope , a . group . Linker , gpath . Join ( a . prefix , itemPath ) , true }
actions = appendIf ( actions , action { "LIST" , resource , params , namer } , isLister )
actions = appendIf ( actions , action { "WATCHLIST" , "watch/" + resource , params , namer } , allowWatchList )
2015-01-31 00:08:59 +00:00
}
2015-06-18 20:20:28 +00:00
break
default :
2015-09-15 03:55:18 +00:00
return nil , fmt . Errorf ( "unsupported restscope: %s" , scope . Name ( ) )
2015-01-31 00:08:59 +00:00
}
// Create Routes for the actions.
// TODO: Add status documentation using Returns()
// Errors (see api/errors/errors.go as well as go-restful router):
// http.StatusNotFound, http.StatusMethodNotAllowed,
// http.StatusUnsupportedMediaType, http.StatusNotAcceptable,
// http.StatusBadRequest, http.StatusUnauthorized, http.StatusForbidden,
// http.StatusRequestTimeout, http.StatusConflict, http.StatusPreconditionFailed,
// 422 (StatusUnprocessableEntity), http.StatusInternalServerError,
// http.StatusServiceUnavailable
// and api error codes
// Note that if we specify a versioned Status object here, we may need to
// create one for the tests, also
// Success:
// http.StatusOK, http.StatusCreated, http.StatusAccepted, http.StatusNoContent
//
// test/integration/auth_test.go is currently the most comprehensive status code test
2015-03-22 03:25:38 +00:00
reqScope := RequestScope {
2016-04-20 17:35:09 +00:00
ContextFunc : ctxFn ,
Serializer : a . group . Serializer ,
ParameterCodec : a . group . ParameterCodec ,
Creater : a . group . Creater ,
Convertor : a . group . Convertor ,
2015-11-30 16:29:19 +00:00
2016-02-24 19:10:37 +00:00
// TODO: This seems wrong for cross-group subresources. It makes an assumption that a subresource and its parent are in the same group version. Revisit this.
2015-11-30 16:29:19 +00:00
Resource : a . group . GroupVersion . WithResource ( resource ) ,
Subresource : subresource ,
2016-02-24 19:10:37 +00:00
Kind : fqKindToRegister ,
2015-03-22 03:25:38 +00:00
}
2015-01-31 00:08:59 +00:00
for _ , action := range actions {
2015-03-22 03:25:38 +00:00
reqScope . Namer = action . Namer
2015-07-14 20:41:13 +00:00
namespaced := ""
2016-03-17 00:46:05 +00:00
if apiResource . Namespaced {
2015-07-14 20:41:13 +00:00
namespaced = "Namespaced"
}
2015-01-31 00:08:59 +00:00
switch action . Verb {
case "GET" : // Get a resource.
2015-04-06 16:58:00 +00:00
var handler restful . RouteFunction
if isGetterWithOptions {
2015-12-21 05:15:35 +00:00
handler = GetResourceWithOptions ( getterWithOptions , reqScope )
2015-04-06 16:58:00 +00:00
} else {
2015-12-01 23:45:29 +00:00
handler = GetResource ( getter , exporter , reqScope )
2015-04-06 16:58:00 +00:00
}
2016-01-12 19:54:48 +00:00
handler = metrics . InstrumentRouteFunc ( action . Verb , resource , handler )
2015-06-08 09:06:01 +00:00
doc := "read the specified " + kind
if hasSubresource {
doc = "read " + subresource + " of the specified " + kind
}
2015-04-06 16:58:00 +00:00
route := ws . GET ( action . Path ) . To ( handler ) .
2015-06-08 09:06:01 +00:00
Doc ( doc ) .
2015-06-08 23:33:58 +00:00
Param ( ws . QueryParameter ( "pretty" , "If 'true', then the output is pretty printed." ) ) .
2015-07-14 20:41:13 +00:00
Operation ( "read" + namespaced + kind + strings . Title ( subresource ) ) .
2015-12-21 05:15:35 +00:00
Produces ( append ( storageMeta . ProducesMIMETypes ( action . Verb ) , a . group . Serializer . SupportedMediaTypes ( ) ... ) ... ) .
2015-04-09 02:47:31 +00:00
Returns ( http . StatusOK , "OK" , versionedObject ) .
2015-01-31 00:08:59 +00:00
Writes ( versionedObject )
2015-04-06 16:58:00 +00:00
if isGetterWithOptions {
2015-06-26 21:10:28 +00:00
if err := addObjectParams ( ws , route , versionedGetOptions ) ; err != nil {
2015-09-15 03:55:18 +00:00
return nil , err
2015-04-06 16:58:00 +00:00
}
}
2015-12-01 23:45:29 +00:00
if isExporter {
if err := addObjectParams ( ws , route , versionedExportOptions ) ; err != nil {
return nil , err
}
}
2015-01-31 00:08:59 +00:00
addParams ( route , action . Params )
ws . Route ( route )
case "LIST" : // List all resources of a kind.
2015-06-08 09:06:01 +00:00
doc := "list objects of kind " + kind
if hasSubresource {
doc = "list " + subresource + " of objects of kind " + kind
}
2016-01-12 19:54:48 +00:00
handler := metrics . InstrumentRouteFunc ( action . Verb , resource , ListResource ( lister , watcher , reqScope , false , a . minRequestTimeout ) )
route := ws . GET ( action . Path ) . To ( handler ) .
2015-06-08 09:06:01 +00:00
Doc ( doc ) .
2015-06-08 23:33:58 +00:00
Param ( ws . QueryParameter ( "pretty" , "If 'true', then the output is pretty printed." ) ) .
2015-07-14 20:41:13 +00:00
Operation ( "list" + namespaced + kind + strings . Title ( subresource ) ) .
2015-12-21 05:15:35 +00:00
Produces ( append ( storageMeta . ProducesMIMETypes ( action . Verb ) , a . group . Serializer . SupportedMediaTypes ( ) ... ) ... ) .
2015-04-09 02:47:31 +00:00
Returns ( http . StatusOK , "OK" , versionedList ) .
2015-01-31 00:08:59 +00:00
Writes ( versionedList )
2015-03-22 21:43:00 +00:00
if err := addObjectParams ( ws , route , versionedListOptions ) ; err != nil {
2015-09-15 03:55:18 +00:00
return nil , err
2015-03-22 21:43:00 +00:00
}
2015-04-07 14:45:18 +00:00
switch {
case isLister && isWatcher :
2015-06-08 09:06:01 +00:00
doc := "list or watch objects of kind " + kind
if hasSubresource {
doc = "list or watch " + subresource + " of objects of kind " + kind
}
route . Doc ( doc )
2015-04-07 14:45:18 +00:00
case isWatcher :
2015-06-08 09:06:01 +00:00
doc := "watch objects of kind " + kind
if hasSubresource {
doc = "watch " + subresource + "of objects of kind " + kind
}
route . Doc ( doc )
2015-04-07 14:45:18 +00:00
}
2015-01-31 00:08:59 +00:00
addParams ( route , action . Params )
ws . Route ( route )
case "PUT" : // Update a resource.
2015-06-08 09:06:01 +00:00
doc := "replace the specified " + kind
if hasSubresource {
doc = "replace " + subresource + " of the specified " + kind
}
2016-01-12 19:54:48 +00:00
handler := metrics . InstrumentRouteFunc ( action . Verb , resource , UpdateResource ( updater , reqScope , a . group . Typer , admit ) )
route := ws . PUT ( action . Path ) . To ( handler ) .
2015-06-08 09:06:01 +00:00
Doc ( doc ) .
2015-06-08 23:33:58 +00:00
Param ( ws . QueryParameter ( "pretty" , "If 'true', then the output is pretty printed." ) ) .
2015-07-14 20:41:13 +00:00
Operation ( "replace" + namespaced + kind + strings . Title ( subresource ) ) .
2015-12-21 05:15:35 +00:00
Produces ( append ( storageMeta . ProducesMIMETypes ( action . Verb ) , a . group . Serializer . SupportedMediaTypes ( ) ... ) ... ) .
2015-04-09 02:47:31 +00:00
Returns ( http . StatusOK , "OK" , versionedObject ) .
Reads ( versionedObject ) .
Writes ( versionedObject )
2015-01-31 00:08:59 +00:00
addParams ( route , action . Params )
ws . Route ( route )
2015-02-21 18:54:48 +00:00
case "PATCH" : // Partially update a resource
2015-06-08 09:06:01 +00:00
doc := "partially update the specified " + kind
if hasSubresource {
doc = "partially update " + subresource + " of the specified " + kind
}
2016-01-12 19:54:48 +00:00
handler := metrics . InstrumentRouteFunc ( action . Verb , resource , PatchResource ( patcher , reqScope , a . group . Typer , admit , mapping . ObjectConvertor ) )
route := ws . PATCH ( action . Path ) . To ( handler ) .
2015-06-08 09:06:01 +00:00
Doc ( doc ) .
2015-06-08 23:33:58 +00:00
Param ( ws . QueryParameter ( "pretty" , "If 'true', then the output is pretty printed." ) ) .
2015-03-14 00:43:14 +00:00
Consumes ( string ( api . JSONPatchType ) , string ( api . MergePatchType ) , string ( api . StrategicMergePatchType ) ) .
2015-07-14 20:41:13 +00:00
Operation ( "patch" + namespaced + kind + strings . Title ( subresource ) ) .
2015-12-21 05:15:35 +00:00
Produces ( append ( storageMeta . ProducesMIMETypes ( action . Verb ) , a . group . Serializer . SupportedMediaTypes ( ) ... ) ... ) .
2015-06-17 19:21:54 +00:00
Returns ( http . StatusOK , "OK" , versionedObject ) .
2015-10-09 03:29:35 +00:00
Reads ( unversioned . Patch { } ) .
2015-04-09 02:47:31 +00:00
Writes ( versionedObject )
2015-02-21 18:54:48 +00:00
addParams ( route , action . Params )
ws . Route ( route )
2015-01-31 00:08:59 +00:00
case "POST" : // Create a resource.
2015-05-04 18:38:41 +00:00
var handler restful . RouteFunction
if isNamedCreater {
handler = CreateNamedResource ( namedCreater , reqScope , a . group . Typer , admit )
} else {
handler = CreateResource ( creater , reqScope , a . group . Typer , admit )
}
2016-01-12 19:54:48 +00:00
handler = metrics . InstrumentRouteFunc ( action . Verb , resource , handler )
2015-06-08 09:06:01 +00:00
doc := "create a " + kind
if hasSubresource {
doc = "create " + subresource + " of a " + kind
}
2015-05-04 18:38:41 +00:00
route := ws . POST ( action . Path ) . To ( handler ) .
2015-06-08 09:06:01 +00:00
Doc ( doc ) .
2015-06-08 23:33:58 +00:00
Param ( ws . QueryParameter ( "pretty" , "If 'true', then the output is pretty printed." ) ) .
2015-07-14 20:41:13 +00:00
Operation ( "create" + namespaced + kind + strings . Title ( subresource ) ) .
2015-12-21 05:15:35 +00:00
Produces ( append ( storageMeta . ProducesMIMETypes ( action . Verb ) , a . group . Serializer . SupportedMediaTypes ( ) ... ) ... ) .
2015-04-09 02:47:31 +00:00
Returns ( http . StatusOK , "OK" , versionedObject ) .
Reads ( versionedObject ) .
Writes ( versionedObject )
2015-01-31 00:08:59 +00:00
addParams ( route , action . Params )
ws . Route ( route )
case "DELETE" : // Delete a resource.
2015-06-08 09:06:01 +00:00
doc := "delete a " + kind
if hasSubresource {
doc = "delete " + subresource + " of a " + kind
}
2016-01-12 19:54:48 +00:00
handler := metrics . InstrumentRouteFunc ( action . Verb , resource , DeleteResource ( gracefulDeleter , isGracefulDeleter , reqScope , admit ) )
route := ws . DELETE ( action . Path ) . To ( handler ) .
2015-06-08 09:06:01 +00:00
Doc ( doc ) .
2015-06-08 23:33:58 +00:00
Param ( ws . QueryParameter ( "pretty" , "If 'true', then the output is pretty printed." ) ) .
2015-07-14 20:41:13 +00:00
Operation ( "delete" + namespaced + kind + strings . Title ( subresource ) ) .
2015-12-21 05:15:35 +00:00
Produces ( append ( storageMeta . ProducesMIMETypes ( action . Verb ) , a . group . Serializer . SupportedMediaTypes ( ) ... ) ... ) .
2015-04-09 02:47:31 +00:00
Writes ( versionedStatus ) .
Returns ( http . StatusOK , "OK" , versionedStatus )
2015-03-05 03:34:31 +00:00
if isGracefulDeleter {
route . Reads ( versionedDeleterObject )
}
2015-01-31 00:08:59 +00:00
addParams ( route , action . Params )
ws . Route ( route )
2015-12-07 09:21:42 +00:00
case "DELETECOLLECTION" :
doc := "delete collection of " + kind
if hasSubresource {
doc = "delete collection of " + subresource + " of a " + kind
}
2016-01-12 19:54:48 +00:00
handler := metrics . InstrumentRouteFunc ( action . Verb , resource , DeleteCollection ( collectionDeleter , isCollectionDeleter , reqScope , admit ) )
route := ws . DELETE ( action . Path ) . To ( handler ) .
2015-12-07 09:21:42 +00:00
Doc ( doc ) .
Param ( ws . QueryParameter ( "pretty" , "If 'true', then the output is pretty printed." ) ) .
Operation ( "deletecollection" + namespaced + kind + strings . Title ( subresource ) ) .
2015-12-21 05:15:35 +00:00
Produces ( append ( storageMeta . ProducesMIMETypes ( action . Verb ) , a . group . Serializer . SupportedMediaTypes ( ) ... ) ... ) .
2015-12-07 09:21:42 +00:00
Writes ( versionedStatus ) .
Returns ( http . StatusOK , "OK" , versionedStatus )
if err := addObjectParams ( ws , route , versionedListOptions ) ; err != nil {
return nil , err
}
addParams ( route , action . Params )
ws . Route ( route )
2015-03-24 04:07:22 +00:00
// TODO: deprecated
2015-01-31 00:08:59 +00:00
case "WATCH" : // Watch a resource.
2015-06-08 09:06:01 +00:00
doc := "watch changes to an object of kind " + kind
if hasSubresource {
doc = "watch changes to " + subresource + " of an object of kind " + kind
}
2016-01-12 19:54:48 +00:00
handler := metrics . InstrumentRouteFunc ( action . Verb , resource , ListResource ( lister , watcher , reqScope , true , a . minRequestTimeout ) )
route := ws . GET ( action . Path ) . To ( handler ) .
2015-06-08 09:06:01 +00:00
Doc ( doc ) .
2015-06-08 23:33:58 +00:00
Param ( ws . QueryParameter ( "pretty" , "If 'true', then the output is pretty printed." ) ) .
2015-07-14 20:41:13 +00:00
Operation ( "watch" + namespaced + kind + strings . Title ( subresource ) ) .
2016-04-20 17:35:09 +00:00
Produces ( a . group . Serializer . SupportedStreamingMediaTypes ( ) ... ) .
2016-04-05 03:07:43 +00:00
Returns ( http . StatusOK , "OK" , versionedWatchEvent ) .
Writes ( versionedWatchEvent )
2015-03-24 04:07:22 +00:00
if err := addObjectParams ( ws , route , versionedListOptions ) ; err != nil {
2015-09-15 03:55:18 +00:00
return nil , err
2015-03-24 04:07:22 +00:00
}
2015-01-31 00:08:59 +00:00
addParams ( route , action . Params )
ws . Route ( route )
2015-03-24 04:07:22 +00:00
// TODO: deprecated
2015-01-31 00:08:59 +00:00
case "WATCHLIST" : // Watch all resources of a kind.
2015-06-08 09:06:01 +00:00
doc := "watch individual changes to a list of " + kind
if hasSubresource {
doc = "watch individual changes to a list of " + subresource + " of " + kind
}
2016-01-12 19:54:48 +00:00
handler := metrics . InstrumentRouteFunc ( action . Verb , resource , ListResource ( lister , watcher , reqScope , true , a . minRequestTimeout ) )
route := ws . GET ( action . Path ) . To ( handler ) .
2015-06-08 09:06:01 +00:00
Doc ( doc ) .
2015-06-08 23:33:58 +00:00
Param ( ws . QueryParameter ( "pretty" , "If 'true', then the output is pretty printed." ) ) .
2015-07-14 20:41:13 +00:00
Operation ( "watch" + namespaced + kind + strings . Title ( subresource ) + "List" ) .
2016-04-20 17:35:09 +00:00
Produces ( a . group . Serializer . SupportedStreamingMediaTypes ( ) ... ) .
2016-04-05 03:07:43 +00:00
Returns ( http . StatusOK , "OK" , versionedWatchEvent ) .
Writes ( versionedWatchEvent )
2015-03-24 04:07:22 +00:00
if err := addObjectParams ( ws , route , versionedListOptions ) ; err != nil {
2015-09-15 03:55:18 +00:00
return nil , err
2015-03-24 04:07:22 +00:00
}
2015-01-31 00:08:59 +00:00
addParams ( route , action . Params )
ws . Route ( route )
2015-12-03 06:57:26 +00:00
// We add "proxy" subresource to remove the need for the generic top level prefix proxy.
// The generic top level prefix proxy is deprecated in v1.2, and will be removed in 1.3, or 1.4 at the latest.
// TODO: DEPRECATED in v1.2.
2015-01-31 00:08:59 +00:00
case "PROXY" : // Proxy requests to a resource.
2015-08-06 01:08:26 +00:00
// Accept all methods as per http://issue.k8s.io/3996
2015-07-14 20:41:13 +00:00
addProxyRoute ( ws , "GET" , a . prefix , action . Path , proxyHandler , namespaced , kind , resource , subresource , hasSubresource , action . Params )
addProxyRoute ( ws , "PUT" , a . prefix , action . Path , proxyHandler , namespaced , kind , resource , subresource , hasSubresource , action . Params )
addProxyRoute ( ws , "POST" , a . prefix , action . Path , proxyHandler , namespaced , kind , resource , subresource , hasSubresource , action . Params )
addProxyRoute ( ws , "DELETE" , a . prefix , action . Path , proxyHandler , namespaced , kind , resource , subresource , hasSubresource , action . Params )
addProxyRoute ( ws , "HEAD" , a . prefix , action . Path , proxyHandler , namespaced , kind , resource , subresource , hasSubresource , action . Params )
addProxyRoute ( ws , "OPTIONS" , a . prefix , action . Path , proxyHandler , namespaced , kind , resource , subresource , hasSubresource , action . Params )
2015-04-14 14:57:00 +00:00
case "CONNECT" :
for _ , method := range connecter . ConnectMethods ( ) {
2015-06-08 09:06:01 +00:00
doc := "connect " + method + " requests to " + kind
if hasSubresource {
doc = "connect " + method + " requests to " + subresource + " of " + kind
}
2016-01-12 19:54:48 +00:00
handler := metrics . InstrumentRouteFunc ( action . Verb , resource , ConnectResource ( connecter , reqScope , admit , path ) )
2015-04-14 14:57:00 +00:00
route := ws . Method ( method ) . Path ( action . Path ) .
2016-01-12 19:54:48 +00:00
To ( handler ) .
2015-06-08 09:06:01 +00:00
Doc ( doc ) .
2015-07-14 20:41:13 +00:00
Operation ( "connect" + strings . Title ( strings . ToLower ( method ) ) + namespaced + kind + strings . Title ( subresource ) ) .
2015-04-14 14:57:00 +00:00
Produces ( "*/*" ) .
Consumes ( "*/*" ) .
Writes ( "string" )
2015-06-26 21:10:28 +00:00
if versionedConnectOptions != nil {
if err := addObjectParams ( ws , route , versionedConnectOptions ) ; err != nil {
2015-09-15 03:55:18 +00:00
return nil , err
2015-04-14 14:57:00 +00:00
}
}
2015-06-17 20:22:44 +00:00
addParams ( route , action . Params )
2015-04-14 14:57:00 +00:00
ws . Route ( route )
}
2015-02-09 14:47:13 +00:00
default :
2015-09-15 03:55:18 +00:00
return nil , fmt . Errorf ( "unrecognized action verb: %s" , action . Verb )
2015-01-31 00:08:59 +00:00
}
// Note: update GetAttribs() when adding a custom handler.
}
2015-09-15 03:55:18 +00:00
return & apiResource , nil
2015-01-31 00:08:59 +00:00
}
2015-02-12 19:21:47 +00:00
// rootScopeNaming reads only names from a request and ignores namespaces. It implements ScopeNamer
// for root scoped resources.
type rootScopeNaming struct {
scope meta . RESTScope
runtime . SelfLinker
itemPath string
}
// rootScopeNaming implements ScopeNamer
var _ ScopeNamer = rootScopeNaming { }
// Namespace returns an empty string because root scoped objects have no namespace.
func ( n rootScopeNaming ) Namespace ( req * restful . Request ) ( namespace string , err error ) {
return "" , nil
}
// Name returns the name from the path and an empty string for namespace, or an error if the
// name is empty.
func ( n rootScopeNaming ) Name ( req * restful . Request ) ( namespace , name string , err error ) {
name = req . PathParameter ( "name" )
if len ( name ) == 0 {
return "" , "" , errEmptyName
}
return "" , name , nil
}
// GenerateLink returns the appropriate path and query to locate an object by its canonical path.
func ( n rootScopeNaming ) GenerateLink ( req * restful . Request , obj runtime . Object ) ( path , query string , err error ) {
_ , name , err := n . ObjectName ( obj )
if err != nil {
return "" , "" , err
}
if len ( name ) == 0 {
_ , name , err = n . Name ( req )
if err != nil {
return "" , "" , err
}
}
path = strings . Replace ( n . itemPath , "{name}" , name , 1 )
return path , "" , nil
}
// GenerateListLink returns the appropriate path and query to locate a list by its canonical path.
func ( n rootScopeNaming ) GenerateListLink ( req * restful . Request ) ( path , query string , err error ) {
path = req . Request . URL . Path
return path , "" , nil
}
// ObjectName returns the name set on the object, or an error if the
// name cannot be returned. Namespace is empty
// TODO: distinguish between objects with name/namespace and without via a specific error.
func ( n rootScopeNaming ) ObjectName ( obj runtime . Object ) ( namespace , name string , err error ) {
name , err = n . SelfLinker . Name ( obj )
if err != nil {
return "" , "" , err
}
if len ( name ) == 0 {
return "" , "" , errEmptyName
}
return "" , name , nil
}
// scopeNaming returns naming information from a request. It implements ScopeNamer for
// namespace scoped resources.
type scopeNaming struct {
scope meta . RESTScope
runtime . SelfLinker
itemPath string
allNamespaces bool
}
// scopeNaming implements ScopeNamer
var _ ScopeNamer = scopeNaming { }
// Namespace returns the namespace from the path or the default.
func ( n scopeNaming ) Namespace ( req * restful . Request ) ( namespace string , err error ) {
if n . allNamespaces {
return "" , nil
}
2015-06-18 20:20:28 +00:00
namespace = req . PathParameter ( n . scope . ArgumentName ( ) )
2015-02-12 19:21:47 +00:00
if len ( namespace ) == 0 {
// a URL was constructed without the namespace, or this method was invoked
// on an object without a namespace path parameter.
return "" , fmt . Errorf ( "no namespace parameter found on request" )
}
return namespace , nil
}
// Name returns the name from the path, the namespace (or default), or an error if the
// name is empty.
func ( n scopeNaming ) Name ( req * restful . Request ) ( namespace , name string , err error ) {
namespace , _ = n . Namespace ( req )
name = req . PathParameter ( "name" )
if len ( name ) == 0 {
return "" , "" , errEmptyName
}
return
}
// GenerateLink returns the appropriate path and query to locate an object by its canonical path.
func ( n scopeNaming ) GenerateLink ( req * restful . Request , obj runtime . Object ) ( path , query string , err error ) {
namespace , name , err := n . ObjectName ( obj )
if err != nil {
return "" , "" , err
}
if len ( namespace ) == 0 && len ( name ) == 0 {
namespace , name , err = n . Name ( req )
if err != nil {
return "" , "" , err
}
}
2015-06-10 20:17:04 +00:00
if len ( name ) == 0 {
return "" , "" , errEmptyName
}
2015-02-12 19:21:47 +00:00
path = strings . Replace ( n . itemPath , "{name}" , name , 1 )
2015-06-18 20:20:28 +00:00
path = strings . Replace ( path , "{" + n . scope . ArgumentName ( ) + "}" , namespace , 1 )
2015-02-12 19:21:47 +00:00
return path , "" , nil
}
// GenerateListLink returns the appropriate path and query to locate a list by its canonical path.
func ( n scopeNaming ) GenerateListLink ( req * restful . Request ) ( path , query string , err error ) {
path = req . Request . URL . Path
return path , "" , nil
}
// ObjectName returns the name and namespace set on the object, or an error if the
// name cannot be returned.
// TODO: distinguish between objects with name/namespace and without via a specific error.
func ( n scopeNaming ) ObjectName ( obj runtime . Object ) ( namespace , name string , err error ) {
name , err = n . SelfLinker . Name ( obj )
if err != nil {
return "" , "" , err
}
namespace , err = n . SelfLinker . Namespace ( obj )
if err != nil {
return "" , "" , err
}
return namespace , name , err
}
2015-01-31 00:08:59 +00:00
// This magic incantation returns *ptrToObject for an arbitrary pointer
func indirectArbitraryPointer ( ptrToObject interface { } ) interface { } {
return reflect . Indirect ( reflect . ValueOf ( ptrToObject ) ) . Interface ( )
}
func appendIf ( actions [ ] action , a action , shouldAppend bool ) [ ] action {
if shouldAppend {
actions = append ( actions , a )
}
return actions
}
2015-02-24 08:37:20 +00:00
// Wraps a http.Handler function inside a restful.RouteFunction
func routeFunction ( handler http . Handler ) restful . RouteFunction {
2015-01-31 00:08:59 +00:00
return func ( restReq * restful . Request , restResp * restful . Response ) {
2015-02-24 08:37:20 +00:00
handler . ServeHTTP ( restResp . ResponseWriter , restReq . Request )
2015-01-31 00:08:59 +00:00
}
}
2015-07-14 20:41:13 +00:00
func addProxyRoute ( ws * restful . WebService , method string , prefix string , path string , proxyHandler http . Handler , namespaced , kind , resource , subresource string , hasSubresource bool , params [ ] * restful . Parameter ) {
2015-06-08 09:06:01 +00:00
doc := "proxy " + method + " requests to " + kind
if hasSubresource {
doc = "proxy " + method + " requests to " + subresource + " of " + kind
}
2016-01-12 19:54:48 +00:00
handler := metrics . InstrumentRouteFunc ( "PROXY" , resource , routeFunction ( proxyHandler ) )
proxyRoute := ws . Method ( method ) . Path ( path ) . To ( handler ) .
2015-06-08 09:06:01 +00:00
Doc ( doc ) .
2015-07-14 20:41:13 +00:00
Operation ( "proxy" + strings . Title ( method ) + namespaced + kind + strings . Title ( subresource ) ) .
2015-01-31 00:08:59 +00:00
Produces ( "*/*" ) .
2015-04-09 02:47:31 +00:00
Consumes ( "*/*" ) .
Writes ( "string" )
2015-01-31 00:08:59 +00:00
addParams ( proxyRoute , params )
ws . Route ( proxyRoute )
}
func addParams ( route * restful . RouteBuilder , params [ ] * restful . Parameter ) {
for _ , param := range params {
route . Param ( param )
}
}
2015-03-22 03:25:38 +00:00
2015-03-25 20:21:40 +00:00
// addObjectParams converts a runtime.Object into a set of go-restful Param() definitions on the route.
// The object must be a pointer to a struct; only fields at the top level of the struct that are not
// themselves interfaces or structs are used; only fields with a json tag that is non empty (the standard
// Go JSON behavior for omitting a field) become query parameters. The name of the query parameter is
// the JSON field name. If a description struct tag is set on the field, that description is used on the
// query parameter. In essence, it converts a standard JSON top level object into a query param schema.
2015-04-09 02:47:31 +00:00
func addObjectParams ( ws * restful . WebService , route * restful . RouteBuilder , obj interface { } ) error {
2015-03-22 21:43:00 +00:00
sv , err := conversion . EnforcePtr ( obj )
if err != nil {
return err
}
st := sv . Type ( )
switch st . Kind ( ) {
case reflect . Struct :
for i := 0 ; i < st . NumField ( ) ; i ++ {
name := st . Field ( i ) . Name
sf , ok := st . FieldByName ( name )
if ! ok {
continue
}
switch sf . Type . Kind ( ) {
case reflect . Interface , reflect . Struct :
default :
jsonTag := sf . Tag . Get ( "json" )
if len ( jsonTag ) == 0 {
continue
}
jsonName := strings . SplitN ( jsonTag , "," , 2 ) [ 0 ]
if len ( jsonName ) == 0 {
continue
}
2015-07-25 01:33:03 +00:00
var desc string
if docable , ok := obj . ( documentable ) ; ok {
desc = docable . SwaggerDoc ( ) [ jsonName ]
}
2015-08-11 01:32:37 +00:00
route . Param ( ws . QueryParameter ( jsonName , desc ) . DataType ( typeToJSON ( sf . Type . String ( ) ) ) )
2015-03-22 21:43:00 +00:00
}
}
}
return nil
}
2015-03-28 04:41:52 +00:00
// TODO: this is incomplete, expand as needed.
// Convert the name of a golang type to the name of a JSON type
func typeToJSON ( typeName string ) string {
switch typeName {
2015-11-04 01:31:40 +00:00
case "bool" , "*bool" :
2015-03-28 04:41:52 +00:00
return "boolean"
2015-11-04 01:31:40 +00:00
case "uint8" , "*uint8" , "int" , "*int" , "int32" , "*int32" , "int64" , "*int64" , "uint32" , "*uint32" , "uint64" , "*uint64" :
2015-03-28 04:41:52 +00:00
return "integer"
2015-11-04 01:31:40 +00:00
case "float64" , "*float64" , "float32" , "*float32" :
2015-03-28 04:41:52 +00:00
return "number"
2015-11-04 01:31:40 +00:00
case "unversioned.Time" , "*unversioned.Time" :
2015-03-28 04:41:52 +00:00
return "string"
2015-11-04 01:31:40 +00:00
case "byte" , "*byte" :
2015-08-11 01:32:37 +00:00
return "string"
2015-11-04 01:31:40 +00:00
case "[]string" , "[]*string" :
2015-08-11 01:32:37 +00:00
// TODO: Fix this when go-restful supports a way to specify an array query param:
// https://github.com/emicklei/go-restful/issues/225
return "string"
2015-03-28 04:41:52 +00:00
default :
return typeName
}
}
2015-03-22 03:25:38 +00:00
// defaultStorageMetadata provides default answers to rest.StorageMetadata.
type defaultStorageMetadata struct { }
// defaultStorageMetadata implements rest.StorageMetadata
var _ rest . StorageMetadata = defaultStorageMetadata { }
func ( defaultStorageMetadata ) ProducesMIMETypes ( verb string ) [ ] string {
return nil
}
2016-02-24 19:10:37 +00:00
// splitSubresource checks if the given storage path is the path of a subresource and returns
// the resource and subresource components.
func splitSubresource ( path string ) ( string , string , error ) {
var resource , subresource string
switch parts := strings . Split ( path , "/" ) ; len ( parts ) {
case 2 :
resource , subresource = parts [ 0 ] , parts [ 1 ]
case 1 :
resource = parts [ 0 ]
default :
// TODO: support deeper paths
return "" , "" , fmt . Errorf ( "api_installer allows only one or two segment paths (resource or resource/subresource)" )
}
return resource , subresource , nil
}