mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-26 21:17:23 +00:00
Merge pull request #3944 from derekwaynecarr/rest_mapper_scope_support
Make RESTMapper scope aware, use it to inform kubectl and swagger behavior
This commit is contained in:
commit
8b2eeafd49
@ -103,7 +103,43 @@ func init() {
|
|||||||
return interfaces, true
|
return interfaces, true
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
mapper.Add(api.Scheme, true, "v1beta1", "v1beta2")
|
// list of versions we support on the server
|
||||||
mapper.Add(api.Scheme, false, "v1beta3")
|
versions := []string{"v1beta1", "v1beta2", "v1beta3"}
|
||||||
|
|
||||||
|
// versions that used mixed case URL formats
|
||||||
|
versionMixedCase := map[string]bool{
|
||||||
|
"v1beta1": true,
|
||||||
|
"v1beta2": true,
|
||||||
|
}
|
||||||
|
|
||||||
|
// backwards compatibility, prior to v1beta3, we identified the namespace as a query parameter
|
||||||
|
versionToNamespaceScope := map[string]meta.RESTScope{
|
||||||
|
"v1beta1": meta.RESTScopeNamespaceLegacy,
|
||||||
|
"v1beta2": meta.RESTScopeNamespaceLegacy,
|
||||||
|
"v1beta3": meta.RESTScopeNamespace,
|
||||||
|
}
|
||||||
|
|
||||||
|
// the list of kinds that are scoped at the root of the api hierarchy
|
||||||
|
// if a kind is not enumerated here, it is assumed to have a namespace scope
|
||||||
|
kindToRootScope := map[string]bool{
|
||||||
|
"Node": true,
|
||||||
|
"Minion": true,
|
||||||
|
}
|
||||||
|
|
||||||
|
// enumerate all supported versions, get the kinds, and register with the mapper how to address our resources
|
||||||
|
for _, version := range versions {
|
||||||
|
for kind := range api.Scheme.KnownTypes(version) {
|
||||||
|
mixedCase, found := versionMixedCase[version]
|
||||||
|
if !found {
|
||||||
|
mixedCase = false
|
||||||
|
}
|
||||||
|
scope := versionToNamespaceScope[version]
|
||||||
|
_, found = kindToRootScope[kind]
|
||||||
|
if found {
|
||||||
|
scope = meta.RESTScopeRoot
|
||||||
|
}
|
||||||
|
mapper.Add(scope, kind, version, mixedCase)
|
||||||
|
}
|
||||||
|
}
|
||||||
RESTMapper = mapper
|
RESTMapper = mapper
|
||||||
}
|
}
|
||||||
|
@ -94,6 +94,30 @@ type MetadataAccessor interface {
|
|||||||
runtime.ResourceVersioner
|
runtime.ResourceVersioner
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type RESTScopeName string
|
||||||
|
|
||||||
|
const (
|
||||||
|
RESTScopeNameNamespace RESTScopeName = "namespace"
|
||||||
|
RESTScopeNameRoot RESTScopeName = "root"
|
||||||
|
)
|
||||||
|
|
||||||
|
// RESTScope contains the information needed to deal with REST resources that are in a resource hierarchy
|
||||||
|
// TODO After we deprecate v1beta1 and v1beta2, we can look a supporting removing the flexibility of supporting
|
||||||
|
// either a query or path param, and instead just support path param
|
||||||
|
type RESTScope interface {
|
||||||
|
// Name of the scope
|
||||||
|
Name() RESTScopeName
|
||||||
|
// ParamName is the optional name of the parameter that should be inserted in the resource url
|
||||||
|
// If empty, no param will be inserted
|
||||||
|
ParamName() string
|
||||||
|
// ParamPath is a boolean that controls how the parameter is manifested in resource paths
|
||||||
|
// If true, this parameter is encoded in path (i.e. /{paramName}/{paramValue})
|
||||||
|
// If false, this parameter is encoded in query (i.e. ?{paramName}={paramValue})
|
||||||
|
ParamPath() bool
|
||||||
|
// ParamDescription is the optional description to use to document the parameter in api documentation
|
||||||
|
ParamDescription() string
|
||||||
|
}
|
||||||
|
|
||||||
// RESTMapping contains the information needed to deal with objects of a specific
|
// RESTMapping contains the information needed to deal with objects of a specific
|
||||||
// resource and kind in a RESTful manner.
|
// resource and kind in a RESTful manner.
|
||||||
type RESTMapping struct {
|
type RESTMapping struct {
|
||||||
@ -104,6 +128,9 @@ type RESTMapping struct {
|
|||||||
APIVersion string
|
APIVersion string
|
||||||
Kind string
|
Kind string
|
||||||
|
|
||||||
|
// Scope contains the information needed to deal with REST Resources that are in a resource hierarchy
|
||||||
|
Scope RESTScope
|
||||||
|
|
||||||
runtime.Codec
|
runtime.Codec
|
||||||
runtime.ObjectConvertor
|
runtime.ObjectConvertor
|
||||||
MetadataAccessor
|
MetadataAccessor
|
||||||
|
@ -19,10 +19,47 @@ package meta
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Implements RESTScope interface
|
||||||
|
type restScope struct {
|
||||||
|
name RESTScopeName
|
||||||
|
paramName string
|
||||||
|
paramPath bool
|
||||||
|
paramDescription string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *restScope) Name() RESTScopeName {
|
||||||
|
return r.name
|
||||||
|
}
|
||||||
|
func (r *restScope) ParamName() string {
|
||||||
|
return r.paramName
|
||||||
|
}
|
||||||
|
func (r *restScope) ParamPath() bool {
|
||||||
|
return r.paramPath
|
||||||
|
}
|
||||||
|
func (r *restScope) ParamDescription() string {
|
||||||
|
return r.paramDescription
|
||||||
|
}
|
||||||
|
|
||||||
|
var RESTScopeNamespaceLegacy = &restScope{
|
||||||
|
name: RESTScopeNameNamespace,
|
||||||
|
paramName: "namespace",
|
||||||
|
paramPath: false,
|
||||||
|
paramDescription: "object name and auth scope, such as for teams and projects",
|
||||||
|
}
|
||||||
|
|
||||||
|
var RESTScopeNamespace = &restScope{
|
||||||
|
name: RESTScopeNameNamespace,
|
||||||
|
paramName: "ns",
|
||||||
|
paramPath: true,
|
||||||
|
paramDescription: "object name and auth scope, such as for teams and projects",
|
||||||
|
}
|
||||||
|
|
||||||
|
var RESTScopeRoot = &restScope{
|
||||||
|
name: RESTScopeNameRoot,
|
||||||
|
}
|
||||||
|
|
||||||
// typeMeta is used as a key for lookup in the mapping between REST path and
|
// typeMeta is used as a key for lookup in the mapping between REST path and
|
||||||
// API object.
|
// API object.
|
||||||
type typeMeta struct {
|
type typeMeta struct {
|
||||||
@ -45,6 +82,7 @@ type typeMeta struct {
|
|||||||
type DefaultRESTMapper struct {
|
type DefaultRESTMapper struct {
|
||||||
mapping map[string]typeMeta
|
mapping map[string]typeMeta
|
||||||
reverse map[typeMeta]string
|
reverse map[typeMeta]string
|
||||||
|
scopes map[typeMeta]RESTScope
|
||||||
versions []string
|
versions []string
|
||||||
interfacesFunc VersionInterfacesFunc
|
interfacesFunc VersionInterfacesFunc
|
||||||
}
|
}
|
||||||
@ -61,40 +99,38 @@ type VersionInterfacesFunc func(apiVersion string) (*VersionInterfaces, bool)
|
|||||||
func NewDefaultRESTMapper(versions []string, f VersionInterfacesFunc) *DefaultRESTMapper {
|
func NewDefaultRESTMapper(versions []string, f VersionInterfacesFunc) *DefaultRESTMapper {
|
||||||
mapping := make(map[string]typeMeta)
|
mapping := make(map[string]typeMeta)
|
||||||
reverse := make(map[typeMeta]string)
|
reverse := make(map[typeMeta]string)
|
||||||
|
scopes := make(map[typeMeta]RESTScope)
|
||||||
// TODO: verify name mappings work correctly when versions differ
|
// TODO: verify name mappings work correctly when versions differ
|
||||||
|
|
||||||
return &DefaultRESTMapper{
|
return &DefaultRESTMapper{
|
||||||
mapping: mapping,
|
mapping: mapping,
|
||||||
reverse: reverse,
|
reverse: reverse,
|
||||||
|
scopes: scopes,
|
||||||
versions: versions,
|
versions: versions,
|
||||||
interfacesFunc: f,
|
interfacesFunc: f,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add adds objects from a runtime.Scheme and its named versions to this map.
|
func (m *DefaultRESTMapper) Add(scope RESTScope, kind string, version string, mixedCase bool) {
|
||||||
// If mixedCase is true, the legacy v1beta1/v1beta2 Kubernetes resource naming convention
|
plural, singular := kindToResource(kind, mixedCase)
|
||||||
// will be applied (camelCase vs lowercase).
|
meta := typeMeta{APIVersion: version, Kind: kind}
|
||||||
func (m *DefaultRESTMapper) Add(scheme *runtime.Scheme, mixedCase bool, versions ...string) {
|
if _, ok := m.mapping[plural]; !ok {
|
||||||
for _, version := range versions {
|
m.mapping[plural] = meta
|
||||||
for kind := range scheme.KnownTypes(version) {
|
m.mapping[singular] = meta
|
||||||
plural, singular := kindToResource(kind, mixedCase)
|
if strings.ToLower(plural) != plural {
|
||||||
meta := typeMeta{APIVersion: version, Kind: kind}
|
m.mapping[strings.ToLower(plural)] = meta
|
||||||
if _, ok := m.mapping[plural]; !ok {
|
m.mapping[strings.ToLower(singular)] = meta
|
||||||
m.mapping[plural] = meta
|
|
||||||
m.mapping[singular] = meta
|
|
||||||
if strings.ToLower(plural) != plural {
|
|
||||||
m.mapping[strings.ToLower(plural)] = meta
|
|
||||||
m.mapping[strings.ToLower(singular)] = meta
|
|
||||||
}
|
|
||||||
}
|
|
||||||
m.reverse[meta] = plural
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
m.reverse[meta] = plural
|
||||||
|
m.scopes[meta] = scope
|
||||||
}
|
}
|
||||||
|
|
||||||
// kindToResource converts Kind to a resource name.
|
// kindToResource converts Kind to a resource name.
|
||||||
func kindToResource(kind string, mixedCase bool) (plural, singular string) {
|
func kindToResource(kind string, mixedCase bool) (plural, singular string) {
|
||||||
|
if len(kind) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
if mixedCase {
|
if mixedCase {
|
||||||
// Legacy support for mixed case names
|
// Legacy support for mixed case names
|
||||||
singular = strings.ToLower(kind[:1]) + kind[1:]
|
singular = strings.ToLower(kind[:1]) + kind[1:]
|
||||||
@ -167,6 +203,12 @@ func (m *DefaultRESTMapper) RESTMapping(kind string, versions ...string) (*RESTM
|
|||||||
return nil, fmt.Errorf("the provided version %q and kind %q cannot be mapped to a supported object", version, kind)
|
return nil, fmt.Errorf("the provided version %q and kind %q cannot be mapped to a supported object", version, kind)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ensure we have a REST scope
|
||||||
|
scope, ok := m.scopes[typeMeta{APIVersion: version, Kind: kind}]
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("the provided version %q and kind %q cannot be mapped to a supported scope", version, kind)
|
||||||
|
}
|
||||||
|
|
||||||
interfaces, ok := m.interfacesFunc(version)
|
interfaces, ok := m.interfacesFunc(version)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf("the provided version %q has no relevant versions", version)
|
return nil, fmt.Errorf("the provided version %q has no relevant versions", version)
|
||||||
@ -176,6 +218,7 @@ func (m *DefaultRESTMapper) RESTMapping(kind string, versions ...string) (*RESTM
|
|||||||
Resource: resource,
|
Resource: resource,
|
||||||
APIVersion: version,
|
APIVersion: version,
|
||||||
Kind: kind,
|
Kind: kind,
|
||||||
|
Scope: scope,
|
||||||
|
|
||||||
Codec: interfaces.Codec,
|
Codec: interfaces.Codec,
|
||||||
ObjectConvertor: interfaces.ObjectConvertor,
|
ObjectConvertor: interfaces.ObjectConvertor,
|
||||||
|
@ -75,10 +75,7 @@ func TestRESTMapperVersionAndKindForResource(t *testing.T) {
|
|||||||
}
|
}
|
||||||
for i, testCase := range testCases {
|
for i, testCase := range testCases {
|
||||||
mapper := NewDefaultRESTMapper([]string{"test"}, fakeInterfaces)
|
mapper := NewDefaultRESTMapper([]string{"test"}, fakeInterfaces)
|
||||||
scheme := runtime.NewScheme()
|
mapper.Add(RESTScopeNamespace, testCase.Kind, testCase.APIVersion, testCase.MixedCase)
|
||||||
scheme.AddKnownTypes("test", &InternalObject{})
|
|
||||||
mapper.Add(scheme, testCase.MixedCase, "test")
|
|
||||||
|
|
||||||
v, k, err := mapper.VersionAndKindForResource(testCase.Resource)
|
v, k, err := mapper.VersionAndKindForResource(testCase.Resource)
|
||||||
hasErr := err != nil
|
hasErr := err != nil
|
||||||
if hasErr != testCase.Err {
|
if hasErr != testCase.Err {
|
||||||
@ -150,10 +147,7 @@ func TestRESTMapperRESTMapping(t *testing.T) {
|
|||||||
}
|
}
|
||||||
for i, testCase := range testCases {
|
for i, testCase := range testCases {
|
||||||
mapper := NewDefaultRESTMapper(testCase.DefaultVersions, fakeInterfaces)
|
mapper := NewDefaultRESTMapper(testCase.DefaultVersions, fakeInterfaces)
|
||||||
scheme := runtime.NewScheme()
|
mapper.Add(RESTScopeNamespace, "InternalObject", "test", testCase.MixedCase)
|
||||||
scheme.AddKnownTypes("test", &InternalObject{})
|
|
||||||
mapper.Add(scheme, testCase.MixedCase, "test")
|
|
||||||
|
|
||||||
mapping, err := mapper.RESTMapping(testCase.Kind, testCase.APIVersions...)
|
mapping, err := mapper.RESTMapping(testCase.Kind, testCase.APIVersions...)
|
||||||
hasErr := err != nil
|
hasErr := err != nil
|
||||||
if hasErr != testCase.Err {
|
if hasErr != testCase.Err {
|
||||||
@ -180,11 +174,8 @@ func TestRESTMapperRESTMapping(t *testing.T) {
|
|||||||
|
|
||||||
func TestRESTMapperRESTMappingSelectsVersion(t *testing.T) {
|
func TestRESTMapperRESTMappingSelectsVersion(t *testing.T) {
|
||||||
mapper := NewDefaultRESTMapper([]string{"test1", "test2"}, fakeInterfaces)
|
mapper := NewDefaultRESTMapper([]string{"test1", "test2"}, fakeInterfaces)
|
||||||
scheme := runtime.NewScheme()
|
mapper.Add(RESTScopeNamespace, "InternalObject", "test1", false)
|
||||||
scheme.AddKnownTypes("test1", &InternalObject{})
|
mapper.Add(RESTScopeNamespace, "OtherObject", "test2", false)
|
||||||
scheme.AddKnownTypeWithName("test2", "OtherObject", &InternalObject{})
|
|
||||||
scheme.AddKnownTypeWithName("test3", "OtherObject", &InternalObject{})
|
|
||||||
mapper.Add(scheme, false, "test1", "test2")
|
|
||||||
|
|
||||||
// pick default matching object kind based on search order
|
// pick default matching object kind based on search order
|
||||||
mapping, err := mapper.RESTMapping("OtherObject")
|
mapping, err := mapper.RESTMapping("OtherObject")
|
||||||
@ -236,10 +227,7 @@ func TestRESTMapperRESTMappingSelectsVersion(t *testing.T) {
|
|||||||
|
|
||||||
func TestRESTMapperReportsErrorOnBadVersion(t *testing.T) {
|
func TestRESTMapperReportsErrorOnBadVersion(t *testing.T) {
|
||||||
mapper := NewDefaultRESTMapper([]string{"test1", "test2"}, unmatchedVersionInterfaces)
|
mapper := NewDefaultRESTMapper([]string{"test1", "test2"}, unmatchedVersionInterfaces)
|
||||||
scheme := runtime.NewScheme()
|
mapper.Add(RESTScopeNamespace, "InternalObject", "test1", false)
|
||||||
scheme.AddKnownTypes("test1", &InternalObject{})
|
|
||||||
mapper.Add(scheme, false, "test1")
|
|
||||||
|
|
||||||
_, err := mapper.RESTMapping("InternalObject", "test1")
|
_, err := mapper.RESTMapping("InternalObject", "test1")
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Errorf("unexpected non-error")
|
t.Errorf("unexpected non-error")
|
||||||
|
@ -29,6 +29,7 @@ import (
|
|||||||
|
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/admission"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/admission"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/meta"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/healthz"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/healthz"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
|
||||||
@ -55,9 +56,9 @@ type defaultAPIServer struct {
|
|||||||
// as RESTful resources at prefix, serialized by codec, and also includes the support
|
// as RESTful resources at prefix, serialized by codec, and also includes the support
|
||||||
// http resources.
|
// http resources.
|
||||||
// Note: This method is used only in tests.
|
// Note: This method is used only in tests.
|
||||||
func Handle(storage map[string]RESTStorage, codec runtime.Codec, root string, version string, selfLinker runtime.SelfLinker, admissionControl admission.Interface) http.Handler {
|
func Handle(storage map[string]RESTStorage, codec runtime.Codec, root string, version string, selfLinker runtime.SelfLinker, admissionControl admission.Interface, mapper meta.RESTMapper) http.Handler {
|
||||||
prefix := root + "/" + version
|
prefix := root + "/" + version
|
||||||
group := NewAPIGroupVersion(storage, codec, prefix, selfLinker, admissionControl)
|
group := NewAPIGroupVersion(storage, codec, prefix, selfLinker, admissionControl, mapper)
|
||||||
container := restful.NewContainer()
|
container := restful.NewContainer()
|
||||||
mux := container.ServeMux
|
mux := container.ServeMux
|
||||||
group.InstallREST(container, mux, root, version)
|
group.InstallREST(container, mux, root, version)
|
||||||
@ -76,6 +77,7 @@ func Handle(storage map[string]RESTStorage, codec runtime.Codec, root string, ve
|
|||||||
// TODO: consider migrating this to go-restful which is a more full-featured version of the same thing.
|
// TODO: consider migrating this to go-restful which is a more full-featured version of the same thing.
|
||||||
type APIGroupVersion struct {
|
type APIGroupVersion struct {
|
||||||
handler RESTHandler
|
handler RESTHandler
|
||||||
|
mapper meta.RESTMapper
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewAPIGroupVersion returns an object that will serve a set of REST resources and their
|
// NewAPIGroupVersion returns an object that will serve a set of REST resources and their
|
||||||
@ -83,15 +85,18 @@ type APIGroupVersion struct {
|
|||||||
// This is a helper method for registering multiple sets of REST handlers under different
|
// This is a helper method for registering multiple sets of REST handlers under different
|
||||||
// prefixes onto a server.
|
// prefixes onto a server.
|
||||||
// TODO: add multitype codec serialization
|
// TODO: add multitype codec serialization
|
||||||
func NewAPIGroupVersion(storage map[string]RESTStorage, codec runtime.Codec, canonicalPrefix string, selfLinker runtime.SelfLinker, admissionControl admission.Interface) *APIGroupVersion {
|
func NewAPIGroupVersion(storage map[string]RESTStorage, codec runtime.Codec, canonicalPrefix string, selfLinker runtime.SelfLinker, admissionControl admission.Interface, mapper meta.RESTMapper) *APIGroupVersion {
|
||||||
return &APIGroupVersion{RESTHandler{
|
return &APIGroupVersion{
|
||||||
storage: storage,
|
handler: RESTHandler{
|
||||||
codec: codec,
|
storage: storage,
|
||||||
canonicalPrefix: canonicalPrefix,
|
codec: codec,
|
||||||
selfLinker: selfLinker,
|
canonicalPrefix: canonicalPrefix,
|
||||||
ops: NewOperations(),
|
selfLinker: selfLinker,
|
||||||
admissionControl: admissionControl,
|
ops: NewOperations(),
|
||||||
}}
|
admissionControl: admissionControl,
|
||||||
|
},
|
||||||
|
mapper: mapper,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// This magic incantation returns *ptrToObject for an arbitrary pointer
|
// This magic incantation returns *ptrToObject for an arbitrary pointer
|
||||||
@ -99,7 +104,7 @@ func indirectArbitraryPointer(ptrToObject interface{}) interface{} {
|
|||||||
return reflect.Indirect(reflect.ValueOf(ptrToObject)).Interface()
|
return reflect.Indirect(reflect.ValueOf(ptrToObject)).Interface()
|
||||||
}
|
}
|
||||||
|
|
||||||
func registerResourceHandlers(ws *restful.WebService, version string, path string, storage RESTStorage, h restful.RouteFunction, namespaceScope bool) error {
|
func registerResourceHandlers(ws *restful.WebService, version string, path string, storage RESTStorage, h restful.RouteFunction, mapper meta.RESTMapper) error {
|
||||||
object := storage.New()
|
object := storage.New()
|
||||||
_, kind, err := api.Scheme.ObjectVersionAndKind(object)
|
_, kind, err := api.Scheme.ObjectVersionAndKind(object)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -111,31 +116,7 @@ func registerResourceHandlers(ws *restful.WebService, version string, path strin
|
|||||||
}
|
}
|
||||||
versionedObject := indirectArbitraryPointer(versionedPtr)
|
versionedObject := indirectArbitraryPointer(versionedPtr)
|
||||||
|
|
||||||
// See github.com/emicklei/go-restful/blob/master/jsr311.go for routing logic
|
var versionedList interface{}
|
||||||
// and status-code behavior
|
|
||||||
if namespaceScope {
|
|
||||||
path = "ns/{namespace}/" + path
|
|
||||||
}
|
|
||||||
|
|
||||||
glog.V(5).Infof("Installing version=/%s, kind=/%s, path=/%s", version, kind, path)
|
|
||||||
|
|
||||||
nameParam := ws.PathParameter("name", "name of the "+kind).DataType("string")
|
|
||||||
namespaceParam := ws.PathParameter("namespace", "object name and auth scope, such as for teams and projects").DataType("string")
|
|
||||||
|
|
||||||
createRoute := ws.POST(path).To(h).
|
|
||||||
Doc("create a " + kind).
|
|
||||||
Operation("create" + kind)
|
|
||||||
addParamIf(createRoute, namespaceParam, namespaceScope)
|
|
||||||
if _, ok := storage.(RESTCreater); ok {
|
|
||||||
ws.Route(createRoute.Reads(versionedObject)) // from the request
|
|
||||||
} else {
|
|
||||||
ws.Route(createRoute.Returns(http.StatusMethodNotAllowed, "creating objects is not supported", nil))
|
|
||||||
}
|
|
||||||
|
|
||||||
listRoute := ws.GET(path).To(h).
|
|
||||||
Doc("list objects of kind " + kind).
|
|
||||||
Operation("list" + kind)
|
|
||||||
addParamIf(listRoute, namespaceParam, namespaceScope)
|
|
||||||
if lister, ok := storage.(RESTLister); ok {
|
if lister, ok := storage.(RESTLister); ok {
|
||||||
list := lister.NewList()
|
list := lister.NewList()
|
||||||
_, listKind, err := api.Scheme.ObjectVersionAndKind(list)
|
_, listKind, err := api.Scheme.ObjectVersionAndKind(list)
|
||||||
@ -143,56 +124,171 @@ func registerResourceHandlers(ws *restful.WebService, version string, path strin
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
versionedList := indirectArbitraryPointer(versionedListPtr)
|
versionedList = indirectArbitraryPointer(versionedListPtr)
|
||||||
glog.V(5).Infoln("type: ", reflect.TypeOf(versionedList))
|
|
||||||
ws.Route(listRoute.Returns(http.StatusOK, "OK", versionedList))
|
|
||||||
} else {
|
|
||||||
ws.Route(listRoute.Returns(http.StatusMethodNotAllowed, "listing objects is not supported", nil))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getRoute := ws.GET(path + "/{name}").To(h).
|
mapping, err := mapper.RESTMapping(kind, version)
|
||||||
Doc("read the specified " + kind).
|
if err != nil {
|
||||||
Operation("read" + kind).
|
glog.V(1).Infof("OH NOES kind %s version %s err: %v", kind, version, err)
|
||||||
Param(nameParam)
|
return err
|
||||||
addParamIf(getRoute, namespaceParam, namespaceScope)
|
}
|
||||||
|
|
||||||
|
// names are always in path
|
||||||
|
nameParam := ws.PathParameter("name", "name of the "+kind).DataType("string")
|
||||||
|
|
||||||
|
// what verbs are supported by the storage, used to know what verbs we support per path
|
||||||
|
storageVerbs := map[string]bool{}
|
||||||
|
if _, ok := storage.(RESTCreater); ok {
|
||||||
|
storageVerbs["RESTCreater"] = true
|
||||||
|
}
|
||||||
|
if _, ok := storage.(RESTLister); ok {
|
||||||
|
storageVerbs["RESTLister"] = true
|
||||||
|
}
|
||||||
if _, ok := storage.(RESTGetter); ok {
|
if _, ok := storage.(RESTGetter); ok {
|
||||||
ws.Route(getRoute.Writes(versionedObject)) // on the response
|
storageVerbs["RESTGetter"] = true
|
||||||
} else {
|
|
||||||
ws.Route(getRoute.Returns(http.StatusMethodNotAllowed, "reading individual objects is not supported", nil))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
updateRoute := ws.PUT(path + "/{name}").To(h).
|
|
||||||
Doc("update the specified " + kind).
|
|
||||||
Operation("update" + kind).
|
|
||||||
Param(nameParam)
|
|
||||||
addParamIf(updateRoute, namespaceParam, namespaceScope)
|
|
||||||
if _, ok := storage.(RESTUpdater); ok {
|
|
||||||
ws.Route(updateRoute.Reads(versionedObject)) // from the request
|
|
||||||
} else {
|
|
||||||
ws.Route(updateRoute.Returns(http.StatusMethodNotAllowed, "updating objects is not supported", nil))
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Support PATCH
|
|
||||||
deleteRoute := ws.DELETE(path + "/{name}").To(h).
|
|
||||||
Doc("delete the specified " + kind).
|
|
||||||
Operation("delete" + kind).
|
|
||||||
Param(nameParam)
|
|
||||||
addParamIf(deleteRoute, namespaceParam, namespaceScope)
|
|
||||||
if _, ok := storage.(RESTDeleter); ok {
|
if _, ok := storage.(RESTDeleter); ok {
|
||||||
ws.Route(deleteRoute)
|
storageVerbs["RESTDeleter"] = true
|
||||||
} else {
|
}
|
||||||
ws.Route(deleteRoute.Returns(http.StatusMethodNotAllowed, "deleting objects is not supported", nil))
|
if _, ok := storage.(RESTUpdater); ok {
|
||||||
|
storageVerbs["RESTUpdater"] = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// path to verbs takes a path, and set of http verbs we should claim to support on this path
|
||||||
|
// given current url formats, this is scope specific
|
||||||
|
// for example, in v1beta3 url styles we would support the following:
|
||||||
|
// /ns/{namespace}/{resource} = []{POST, GET} // list resources in this namespace scope
|
||||||
|
// /ns/{namespace}/{resource}/{name} = []{GET, PUT, DELETE} // handle an individual resource
|
||||||
|
// /{resource} = []{GET} // list resources across all namespaces
|
||||||
|
pathToVerbs := map[string][]string{}
|
||||||
|
// similar to the above, it maps the ordered set of parameters that need to be documented
|
||||||
|
pathToParam := map[string][]*restful.Parameter{}
|
||||||
|
// pathToStorageVerb lets us distinguish between RESTLister and RESTGetter on verb=GET
|
||||||
|
pathToStorageVerb := map[string]string{}
|
||||||
|
scope := mapping.Scope
|
||||||
|
if scope.Name() != meta.RESTScopeNameNamespace {
|
||||||
|
// non-namespaced resources support a smaller set of resource paths
|
||||||
|
resourcesPath := path
|
||||||
|
resourcesPathVerbs := []string{}
|
||||||
|
resourcesPathVerbs = appendStringIf(resourcesPathVerbs, "POST", storageVerbs["RESTCreater"])
|
||||||
|
resourcesPathVerbs = appendStringIf(resourcesPathVerbs, "GET", storageVerbs["RESTLister"])
|
||||||
|
pathToVerbs[resourcesPath] = resourcesPathVerbs
|
||||||
|
pathToParam[resourcesPath] = []*restful.Parameter{}
|
||||||
|
pathToStorageVerb[resourcesPath] = "RESTLister"
|
||||||
|
|
||||||
|
itemPath := resourcesPath + "/{name}"
|
||||||
|
itemPathVerbs := []string{}
|
||||||
|
itemPathVerbs = appendStringIf(itemPathVerbs, "GET", storageVerbs["RESTGetter"])
|
||||||
|
itemPathVerbs = appendStringIf(itemPathVerbs, "PUT", storageVerbs["RESTUpdater"])
|
||||||
|
itemPathVerbs = appendStringIf(itemPathVerbs, "DELETE", storageVerbs["RESTDeleter"])
|
||||||
|
pathToVerbs[itemPath] = itemPathVerbs
|
||||||
|
pathToParam[itemPath] = []*restful.Parameter{nameParam}
|
||||||
|
pathToStorageVerb[itemPath] = "RESTGetter"
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// v1beta3 format with namespace in path
|
||||||
|
if scope.ParamPath() {
|
||||||
|
namespaceParam := ws.PathParameter(scope.ParamName(), scope.ParamDescription()).DataType("string")
|
||||||
|
|
||||||
|
resourceByNamespace := scope.ParamName() + "/{" + scope.ParamName() + "}/" + path
|
||||||
|
resourceByNamespaceVerbs := []string{}
|
||||||
|
resourceByNamespaceVerbs = appendStringIf(resourceByNamespaceVerbs, "POST", storageVerbs["RESTCreater"])
|
||||||
|
resourceByNamespaceVerbs = appendStringIf(resourceByNamespaceVerbs, "GET", storageVerbs["RESTLister"])
|
||||||
|
pathToVerbs[resourceByNamespace] = resourceByNamespaceVerbs
|
||||||
|
pathToParam[resourceByNamespace] = []*restful.Parameter{namespaceParam}
|
||||||
|
pathToStorageVerb[resourceByNamespace] = "RESTLister"
|
||||||
|
|
||||||
|
itemPath := resourceByNamespace + "/{name}"
|
||||||
|
itemPathVerbs := []string{}
|
||||||
|
itemPathVerbs = appendStringIf(itemPathVerbs, "GET", storageVerbs["RESTGetter"])
|
||||||
|
itemPathVerbs = appendStringIf(itemPathVerbs, "PUT", storageVerbs["RESTUpdater"])
|
||||||
|
itemPathVerbs = appendStringIf(itemPathVerbs, "DELETE", storageVerbs["RESTDeleter"])
|
||||||
|
pathToVerbs[itemPath] = itemPathVerbs
|
||||||
|
pathToParam[itemPath] = []*restful.Parameter{namespaceParam, nameParam}
|
||||||
|
pathToStorageVerb[itemPath] = "RESTGetter"
|
||||||
|
|
||||||
|
listAcrossNamespace := path
|
||||||
|
listAcrossNamespaceVerbs := []string{}
|
||||||
|
listAcrossNamespaceVerbs = appendStringIf(listAcrossNamespaceVerbs, "GET", storageVerbs["RESTLister"])
|
||||||
|
pathToVerbs[listAcrossNamespace] = listAcrossNamespaceVerbs
|
||||||
|
pathToParam[listAcrossNamespace] = []*restful.Parameter{}
|
||||||
|
pathToStorageVerb[listAcrossNamespace] = "RESTLister"
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// v1beta1/v1beta2 format where namespace was a query parameter
|
||||||
|
namespaceParam := ws.QueryParameter(scope.ParamName(), scope.ParamDescription()).DataType("string")
|
||||||
|
|
||||||
|
resourcesPath := path
|
||||||
|
resourcesPathVerbs := []string{}
|
||||||
|
resourcesPathVerbs = appendStringIf(resourcesPathVerbs, "POST", storageVerbs["RESTCreater"])
|
||||||
|
resourcesPathVerbs = appendStringIf(resourcesPathVerbs, "GET", storageVerbs["RESTLister"])
|
||||||
|
pathToVerbs[resourcesPath] = resourcesPathVerbs
|
||||||
|
pathToParam[resourcesPath] = []*restful.Parameter{namespaceParam}
|
||||||
|
pathToStorageVerb[resourcesPath] = "RESTLister"
|
||||||
|
|
||||||
|
itemPath := resourcesPath + "/{name}"
|
||||||
|
itemPathVerbs := []string{}
|
||||||
|
itemPathVerbs = appendStringIf(itemPathVerbs, "GET", storageVerbs["RESTGetter"])
|
||||||
|
itemPathVerbs = appendStringIf(itemPathVerbs, "PUT", storageVerbs["RESTUpdater"])
|
||||||
|
itemPathVerbs = appendStringIf(itemPathVerbs, "DELETE", storageVerbs["RESTDeleter"])
|
||||||
|
pathToVerbs[itemPath] = itemPathVerbs
|
||||||
|
pathToParam[itemPath] = []*restful.Parameter{namespaceParam, nameParam}
|
||||||
|
pathToStorageVerb[itemPath] = "RESTGetter"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// See github.com/emicklei/go-restful/blob/master/jsr311.go for routing logic
|
||||||
|
// and status-code behavior
|
||||||
|
for path, verbs := range pathToVerbs {
|
||||||
|
glog.V(5).Infof("Installing version=/%s, kind=/%s, path=/%s", version, kind, path)
|
||||||
|
|
||||||
|
params := pathToParam[path]
|
||||||
|
for _, verb := range verbs {
|
||||||
|
var route *restful.RouteBuilder
|
||||||
|
switch verb {
|
||||||
|
case "POST":
|
||||||
|
route = ws.POST(path).To(h).
|
||||||
|
Doc("create a " + kind).
|
||||||
|
Operation("create" + kind).Reads(versionedObject)
|
||||||
|
case "PUT":
|
||||||
|
route = ws.PUT(path).To(h).
|
||||||
|
Doc("update the specified " + kind).
|
||||||
|
Operation("update" + kind).Reads(versionedObject)
|
||||||
|
case "DELETE":
|
||||||
|
route = ws.DELETE(path).To(h).
|
||||||
|
Doc("delete the specified " + kind).
|
||||||
|
Operation("delete" + kind)
|
||||||
|
case "GET":
|
||||||
|
doc := "read the specified " + kind
|
||||||
|
op := "read" + kind
|
||||||
|
if pathToStorageVerb[path] == "RESTLister" {
|
||||||
|
doc = "list objects of kind " + kind
|
||||||
|
op = "list" + kind
|
||||||
|
}
|
||||||
|
route = ws.GET(path).To(h).
|
||||||
|
Doc(doc).
|
||||||
|
Operation(op)
|
||||||
|
if pathToStorageVerb[path] == "RESTLister" {
|
||||||
|
route = route.Returns(http.StatusOK, "OK", versionedList)
|
||||||
|
} else {
|
||||||
|
route = route.Writes(versionedObject)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for paramIndex := range params {
|
||||||
|
route.Param(params[paramIndex])
|
||||||
|
}
|
||||||
|
ws.Route(route)
|
||||||
|
}
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Adds the given param to the given route builder if shouldAdd is true. Does nothing if shouldAdd is false.
|
// appendStringIf appends the value to the slice if shouldAdd is true
|
||||||
func addParamIf(b *restful.RouteBuilder, parameter *restful.Parameter, shouldAdd bool) *restful.RouteBuilder {
|
func appendStringIf(slice []string, value string, shouldAdd bool) []string {
|
||||||
if !shouldAdd {
|
if shouldAdd {
|
||||||
return b
|
return append(slice, value)
|
||||||
}
|
}
|
||||||
return b.Param(parameter)
|
return slice
|
||||||
}
|
}
|
||||||
|
|
||||||
// InstallREST registers the REST handlers (storage, watch, proxy and redirect) into a restful Container.
|
// InstallREST registers the REST handlers (storage, watch, proxy and redirect) into a restful Container.
|
||||||
@ -252,12 +348,7 @@ func (g *APIGroupVersion) InstallREST(container *restful.Container, mux Mux, roo
|
|||||||
registrationErrors := make([]error, 0)
|
registrationErrors := make([]error, 0)
|
||||||
|
|
||||||
for path, storage := range g.handler.storage {
|
for path, storage := range g.handler.storage {
|
||||||
// register legacy patterns where namespace is optional in path
|
if err := registerResourceHandlers(ws, version, path, storage, h, g.mapper); err != nil {
|
||||||
if err := registerResourceHandlers(ws, version, path, storage, h, false); err != nil {
|
|
||||||
registrationErrors = append(registrationErrors, err)
|
|
||||||
}
|
|
||||||
// register pattern where namespace is required in path
|
|
||||||
if err := registerResourceHandlers(ws, version, path, storage, h, true); err != nil {
|
|
||||||
registrationErrors = append(registrationErrors, err)
|
registrationErrors = append(registrationErrors, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -93,7 +93,15 @@ func init() {
|
|||||||
return interfaces, true
|
return interfaces, true
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
defMapper.Add(api.Scheme, true, versions...)
|
// enumerate all supported versions, get the kinds, and register with the mapper how to address our resources
|
||||||
|
for _, version := range versions {
|
||||||
|
for kind := range api.Scheme.KnownTypes(version) {
|
||||||
|
mixedCase := true
|
||||||
|
scope := meta.RESTScopeNamespaceLegacy
|
||||||
|
defMapper.Add(scope, kind, version, mixedCase)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
mapper = defMapper
|
mapper = defMapper
|
||||||
admissionControl = admit.NewAlwaysAdmit()
|
admissionControl = admit.NewAlwaysAdmit()
|
||||||
}
|
}
|
||||||
@ -270,7 +278,7 @@ func TestNotFound(t *testing.T) {
|
|||||||
}
|
}
|
||||||
handler := Handle(map[string]RESTStorage{
|
handler := Handle(map[string]RESTStorage{
|
||||||
"foo": &SimpleRESTStorage{},
|
"foo": &SimpleRESTStorage{},
|
||||||
}, codec, "/prefix", testVersion, selfLinker, admissionControl)
|
}, codec, "/prefix", testVersion, selfLinker, admissionControl, mapper)
|
||||||
server := httptest.NewServer(handler)
|
server := httptest.NewServer(handler)
|
||||||
defer server.Close()
|
defer server.Close()
|
||||||
client := http.Client{}
|
client := http.Client{}
|
||||||
@ -287,6 +295,7 @@ func TestNotFound(t *testing.T) {
|
|||||||
|
|
||||||
if response.StatusCode != v.Status {
|
if response.StatusCode != v.Status {
|
||||||
t.Errorf("Expected %d for %s (%s), Got %#v", v.Status, v.Method, k, response)
|
t.Errorf("Expected %d for %s (%s), Got %#v", v.Status, v.Method, k, response)
|
||||||
|
t.Errorf("MAPPER: %v", mapper)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -297,25 +306,31 @@ func (UnimplementedRESTStorage) New() runtime.Object {
|
|||||||
return &Simple{}
|
return &Simple{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMethodNotAllowed(t *testing.T) {
|
// TestUnimplementedRESTStorage ensures that if a RESTStorage does not implement a given
|
||||||
|
// method, that it is literally not registered with the server. In the past,
|
||||||
|
// we registered everything, and returned method not supported if it didn't support
|
||||||
|
// a verb. Now we literally do not register a storage if it does not implement anything.
|
||||||
|
// TODO: in future, we should update proxy/redirect
|
||||||
|
func TestUnimplementedRESTStorage(t *testing.T) {
|
||||||
type T struct {
|
type T struct {
|
||||||
Method string
|
Method string
|
||||||
Path string
|
Path string
|
||||||
|
ErrCode int
|
||||||
}
|
}
|
||||||
cases := map[string]T{
|
cases := map[string]T{
|
||||||
"GET object": {"GET", "/prefix/version/foo/bar"},
|
"GET object": {"GET", "/prefix/version/foo/bar", http.StatusNotFound},
|
||||||
"GET list": {"GET", "/prefix/version/foo"},
|
"GET list": {"GET", "/prefix/version/foo", http.StatusNotFound},
|
||||||
"POST list": {"POST", "/prefix/version/foo"},
|
"POST list": {"POST", "/prefix/version/foo", http.StatusNotFound},
|
||||||
"PUT object": {"PUT", "/prefix/version/foo/bar"},
|
"PUT object": {"PUT", "/prefix/version/foo/bar", http.StatusNotFound},
|
||||||
"DELETE object": {"DELETE", "/prefix/version/foo/bar"},
|
"DELETE object": {"DELETE", "/prefix/version/foo/bar", http.StatusNotFound},
|
||||||
//"watch list": {"GET", "/prefix/version/watch/foo"},
|
//"watch list": {"GET", "/prefix/version/watch/foo"},
|
||||||
//"watch object": {"GET", "/prefix/version/watch/foo/bar"},
|
//"watch object": {"GET", "/prefix/version/watch/foo/bar"},
|
||||||
"proxy object": {"GET", "/prefix/version/proxy/foo/bar"},
|
"proxy object": {"GET", "/prefix/version/proxy/foo/bar", http.StatusMethodNotAllowed},
|
||||||
"redirect object": {"GET", "/prefix/version/redirect/foo/bar"},
|
"redirect object": {"GET", "/prefix/version/redirect/foo/bar", http.StatusMethodNotAllowed},
|
||||||
}
|
}
|
||||||
handler := Handle(map[string]RESTStorage{
|
handler := Handle(map[string]RESTStorage{
|
||||||
"foo": UnimplementedRESTStorage{},
|
"foo": UnimplementedRESTStorage{},
|
||||||
}, codec, "/prefix", testVersion, selfLinker, admissionControl)
|
}, codec, "/prefix", testVersion, selfLinker, admissionControl, mapper)
|
||||||
server := httptest.NewServer(handler)
|
server := httptest.NewServer(handler)
|
||||||
defer server.Close()
|
defer server.Close()
|
||||||
client := http.Client{}
|
client := http.Client{}
|
||||||
@ -333,28 +348,15 @@ func TestMethodNotAllowed(t *testing.T) {
|
|||||||
defer response.Body.Close()
|
defer response.Body.Close()
|
||||||
data, _ := ioutil.ReadAll(response.Body)
|
data, _ := ioutil.ReadAll(response.Body)
|
||||||
t.Logf("resp: %s", string(data))
|
t.Logf("resp: %s", string(data))
|
||||||
if response.StatusCode != http.StatusMethodNotAllowed {
|
if response.StatusCode != v.ErrCode {
|
||||||
t.Errorf("%s: expected %d for %s, Got %s", k, http.StatusMethodNotAllowed, v.Method, string(data))
|
t.Errorf("%s: expected %d for %s, Got %s", k, http.StatusNotFound, v.Method, string(data))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
obj, err := codec.Decode(data)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("%s: unexpected decode error: %v", k, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
status, ok := obj.(*api.Status)
|
|
||||||
if !ok {
|
|
||||||
t.Errorf("%s: unexpected object: %#v", k, obj)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if status.Reason != api.StatusReasonMethodNotAllowed {
|
|
||||||
t.Errorf("%s: unexpected status: %#v", k, status)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestVersion(t *testing.T) {
|
func TestVersion(t *testing.T) {
|
||||||
handler := Handle(map[string]RESTStorage{}, codec, "/prefix", testVersion, selfLinker, admissionControl)
|
handler := Handle(map[string]RESTStorage{}, codec, "/prefix", testVersion, selfLinker, admissionControl, mapper)
|
||||||
server := httptest.NewServer(handler)
|
server := httptest.NewServer(handler)
|
||||||
defer server.Close()
|
defer server.Close()
|
||||||
client := http.Client{}
|
client := http.Client{}
|
||||||
@ -389,7 +391,7 @@ func TestSimpleList(t *testing.T) {
|
|||||||
namespace: "other",
|
namespace: "other",
|
||||||
expectedSet: "/prefix/version/simple?namespace=other",
|
expectedSet: "/prefix/version/simple?namespace=other",
|
||||||
}
|
}
|
||||||
handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl)
|
handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl, mapper)
|
||||||
server := httptest.NewServer(handler)
|
server := httptest.NewServer(handler)
|
||||||
defer server.Close()
|
defer server.Close()
|
||||||
|
|
||||||
@ -412,7 +414,7 @@ func TestErrorList(t *testing.T) {
|
|||||||
errors: map[string]error{"list": fmt.Errorf("test Error")},
|
errors: map[string]error{"list": fmt.Errorf("test Error")},
|
||||||
}
|
}
|
||||||
storage["simple"] = &simpleStorage
|
storage["simple"] = &simpleStorage
|
||||||
handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl)
|
handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl, mapper)
|
||||||
server := httptest.NewServer(handler)
|
server := httptest.NewServer(handler)
|
||||||
defer server.Close()
|
defer server.Close()
|
||||||
|
|
||||||
@ -438,7 +440,7 @@ func TestNonEmptyList(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
storage["simple"] = &simpleStorage
|
storage["simple"] = &simpleStorage
|
||||||
handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl)
|
handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl, mapper)
|
||||||
server := httptest.NewServer(handler)
|
server := httptest.NewServer(handler)
|
||||||
defer server.Close()
|
defer server.Close()
|
||||||
|
|
||||||
@ -484,7 +486,7 @@ func TestGet(t *testing.T) {
|
|||||||
expectedSet: "/prefix/version/simple/id",
|
expectedSet: "/prefix/version/simple/id",
|
||||||
}
|
}
|
||||||
storage["simple"] = &simpleStorage
|
storage["simple"] = &simpleStorage
|
||||||
handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl)
|
handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl, mapper)
|
||||||
server := httptest.NewServer(handler)
|
server := httptest.NewServer(handler)
|
||||||
defer server.Close()
|
defer server.Close()
|
||||||
|
|
||||||
@ -509,7 +511,7 @@ func TestGetMissing(t *testing.T) {
|
|||||||
errors: map[string]error{"get": apierrs.NewNotFound("simple", "id")},
|
errors: map[string]error{"get": apierrs.NewNotFound("simple", "id")},
|
||||||
}
|
}
|
||||||
storage["simple"] = &simpleStorage
|
storage["simple"] = &simpleStorage
|
||||||
handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl)
|
handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl, mapper)
|
||||||
server := httptest.NewServer(handler)
|
server := httptest.NewServer(handler)
|
||||||
defer server.Close()
|
defer server.Close()
|
||||||
|
|
||||||
@ -528,7 +530,7 @@ func TestDelete(t *testing.T) {
|
|||||||
simpleStorage := SimpleRESTStorage{}
|
simpleStorage := SimpleRESTStorage{}
|
||||||
ID := "id"
|
ID := "id"
|
||||||
storage["simple"] = &simpleStorage
|
storage["simple"] = &simpleStorage
|
||||||
handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl)
|
handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl, mapper)
|
||||||
server := httptest.NewServer(handler)
|
server := httptest.NewServer(handler)
|
||||||
defer server.Close()
|
defer server.Close()
|
||||||
|
|
||||||
@ -549,7 +551,7 @@ func TestDeleteInvokesAdmissionControl(t *testing.T) {
|
|||||||
simpleStorage := SimpleRESTStorage{}
|
simpleStorage := SimpleRESTStorage{}
|
||||||
ID := "id"
|
ID := "id"
|
||||||
storage["simple"] = &simpleStorage
|
storage["simple"] = &simpleStorage
|
||||||
handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, deny.NewAlwaysDeny())
|
handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, deny.NewAlwaysDeny(), mapper)
|
||||||
server := httptest.NewServer(handler)
|
server := httptest.NewServer(handler)
|
||||||
defer server.Close()
|
defer server.Close()
|
||||||
|
|
||||||
@ -571,7 +573,7 @@ func TestDeleteMissing(t *testing.T) {
|
|||||||
errors: map[string]error{"delete": apierrs.NewNotFound("simple", ID)},
|
errors: map[string]error{"delete": apierrs.NewNotFound("simple", ID)},
|
||||||
}
|
}
|
||||||
storage["simple"] = &simpleStorage
|
storage["simple"] = &simpleStorage
|
||||||
handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl)
|
handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl, mapper)
|
||||||
server := httptest.NewServer(handler)
|
server := httptest.NewServer(handler)
|
||||||
defer server.Close()
|
defer server.Close()
|
||||||
|
|
||||||
@ -596,7 +598,7 @@ func TestUpdate(t *testing.T) {
|
|||||||
t: t,
|
t: t,
|
||||||
expectedSet: "/prefix/version/simple/" + ID,
|
expectedSet: "/prefix/version/simple/" + ID,
|
||||||
}
|
}
|
||||||
handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl)
|
handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl, mapper)
|
||||||
server := httptest.NewServer(handler)
|
server := httptest.NewServer(handler)
|
||||||
defer server.Close()
|
defer server.Close()
|
||||||
|
|
||||||
@ -633,7 +635,7 @@ func TestUpdateInvokesAdmissionControl(t *testing.T) {
|
|||||||
t: t,
|
t: t,
|
||||||
expectedSet: "/prefix/version/simple/" + ID,
|
expectedSet: "/prefix/version/simple/" + ID,
|
||||||
}
|
}
|
||||||
handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, deny.NewAlwaysDeny())
|
handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, deny.NewAlwaysDeny(), mapper)
|
||||||
server := httptest.NewServer(handler)
|
server := httptest.NewServer(handler)
|
||||||
defer server.Close()
|
defer server.Close()
|
||||||
|
|
||||||
@ -664,7 +666,7 @@ func TestUpdateMissing(t *testing.T) {
|
|||||||
errors: map[string]error{"update": apierrs.NewNotFound("simple", ID)},
|
errors: map[string]error{"update": apierrs.NewNotFound("simple", ID)},
|
||||||
}
|
}
|
||||||
storage["simple"] = &simpleStorage
|
storage["simple"] = &simpleStorage
|
||||||
handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl)
|
handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl, mapper)
|
||||||
server := httptest.NewServer(handler)
|
server := httptest.NewServer(handler)
|
||||||
defer server.Close()
|
defer server.Close()
|
||||||
|
|
||||||
@ -695,7 +697,7 @@ func TestCreateNotFound(t *testing.T) {
|
|||||||
// See https://github.com/GoogleCloudPlatform/kubernetes/pull/486#discussion_r15037092.
|
// See https://github.com/GoogleCloudPlatform/kubernetes/pull/486#discussion_r15037092.
|
||||||
errors: map[string]error{"create": apierrs.NewNotFound("simple", "id")},
|
errors: map[string]error{"create": apierrs.NewNotFound("simple", "id")},
|
||||||
},
|
},
|
||||||
}, codec, "/prefix", testVersion, selfLinker, admissionControl)
|
}, codec, "/prefix", testVersion, selfLinker, admissionControl, mapper)
|
||||||
server := httptest.NewServer(handler)
|
server := httptest.NewServer(handler)
|
||||||
defer server.Close()
|
defer server.Close()
|
||||||
client := http.Client{}
|
client := http.Client{}
|
||||||
@ -759,11 +761,11 @@ func TestCreate(t *testing.T) {
|
|||||||
t: t,
|
t: t,
|
||||||
name: "bar",
|
name: "bar",
|
||||||
namespace: "other",
|
namespace: "other",
|
||||||
expectedSet: "/prefix/version/ns/other/foo/bar",
|
expectedSet: "/prefix/version/foo/bar?namespace=other",
|
||||||
}
|
}
|
||||||
handler := Handle(map[string]RESTStorage{
|
handler := Handle(map[string]RESTStorage{
|
||||||
"foo": &storage,
|
"foo": &storage,
|
||||||
}, codec, "/prefix", testVersion, selfLinker, admissionControl)
|
}, codec, "/prefix", testVersion, selfLinker, admissionControl, mapper)
|
||||||
server := httptest.NewServer(handler)
|
server := httptest.NewServer(handler)
|
||||||
defer server.Close()
|
defer server.Close()
|
||||||
client := http.Client{}
|
client := http.Client{}
|
||||||
@ -772,7 +774,7 @@ func TestCreate(t *testing.T) {
|
|||||||
Other: "bar",
|
Other: "bar",
|
||||||
}
|
}
|
||||||
data, _ := codec.Encode(simple)
|
data, _ := codec.Encode(simple)
|
||||||
request, err := http.NewRequest("POST", server.URL+"/prefix/version/ns/other/foo", bytes.NewBuffer(data))
|
request, err := http.NewRequest("POST", server.URL+"/prefix/version/foo?namespace=other", bytes.NewBuffer(data))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("unexpected error: %v", err)
|
t.Errorf("unexpected error: %v", err)
|
||||||
}
|
}
|
||||||
@ -817,11 +819,11 @@ func TestCreateInvokesAdmissionControl(t *testing.T) {
|
|||||||
t: t,
|
t: t,
|
||||||
name: "bar",
|
name: "bar",
|
||||||
namespace: "other",
|
namespace: "other",
|
||||||
expectedSet: "/prefix/version/ns/other/foo/bar",
|
expectedSet: "/prefix/version/foo/bar?namespace=other",
|
||||||
}
|
}
|
||||||
handler := Handle(map[string]RESTStorage{
|
handler := Handle(map[string]RESTStorage{
|
||||||
"foo": &storage,
|
"foo": &storage,
|
||||||
}, codec, "/prefix", testVersion, selfLinker, deny.NewAlwaysDeny())
|
}, codec, "/prefix", testVersion, selfLinker, deny.NewAlwaysDeny(), mapper)
|
||||||
server := httptest.NewServer(handler)
|
server := httptest.NewServer(handler)
|
||||||
defer server.Close()
|
defer server.Close()
|
||||||
client := http.Client{}
|
client := http.Client{}
|
||||||
@ -830,7 +832,7 @@ func TestCreateInvokesAdmissionControl(t *testing.T) {
|
|||||||
Other: "bar",
|
Other: "bar",
|
||||||
}
|
}
|
||||||
data, _ := codec.Encode(simple)
|
data, _ := codec.Encode(simple)
|
||||||
request, err := http.NewRequest("POST", server.URL+"/prefix/version/ns/other/foo", bytes.NewBuffer(data))
|
request, err := http.NewRequest("POST", server.URL+"/prefix/version/foo?namespace=other", bytes.NewBuffer(data))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("unexpected error: %v", err)
|
t.Errorf("unexpected error: %v", err)
|
||||||
}
|
}
|
||||||
@ -881,7 +883,7 @@ func TestDelayReturnsError(t *testing.T) {
|
|||||||
return nil, apierrs.NewAlreadyExists("foo", "bar")
|
return nil, apierrs.NewAlreadyExists("foo", "bar")
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
handler := Handle(map[string]RESTStorage{"foo": &storage}, codec, "/prefix", testVersion, selfLinker, admissionControl)
|
handler := Handle(map[string]RESTStorage{"foo": &storage}, codec, "/prefix", testVersion, selfLinker, admissionControl, mapper)
|
||||||
server := httptest.NewServer(handler)
|
server := httptest.NewServer(handler)
|
||||||
defer server.Close()
|
defer server.Close()
|
||||||
|
|
||||||
@ -947,7 +949,7 @@ func TestCreateTimeout(t *testing.T) {
|
|||||||
}
|
}
|
||||||
handler := Handle(map[string]RESTStorage{
|
handler := Handle(map[string]RESTStorage{
|
||||||
"foo": &storage,
|
"foo": &storage,
|
||||||
}, codec, "/prefix", testVersion, selfLinker, admissionControl)
|
}, codec, "/prefix", testVersion, selfLinker, admissionControl, mapper)
|
||||||
server := httptest.NewServer(handler)
|
server := httptest.NewServer(handler)
|
||||||
defer server.Close()
|
defer server.Close()
|
||||||
|
|
||||||
@ -979,7 +981,7 @@ func TestCORSAllowedOrigins(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handler := CORS(
|
handler := CORS(
|
||||||
Handle(map[string]RESTStorage{}, codec, "/prefix", testVersion, selfLinker, admissionControl),
|
Handle(map[string]RESTStorage{}, codec, "/prefix", testVersion, selfLinker, admissionControl, mapper),
|
||||||
allowedOriginRegexps, nil, nil, "true",
|
allowedOriginRegexps, nil, nil, "true",
|
||||||
)
|
)
|
||||||
server := httptest.NewServer(handler)
|
server := httptest.NewServer(handler)
|
||||||
|
@ -230,7 +230,7 @@ func TestProxy(t *testing.T) {
|
|||||||
}
|
}
|
||||||
handler := Handle(map[string]RESTStorage{
|
handler := Handle(map[string]RESTStorage{
|
||||||
"foo": simpleStorage,
|
"foo": simpleStorage,
|
||||||
}, codec, "/prefix", "version", selfLinker, admissionControl)
|
}, codec, "/prefix", "version", selfLinker, admissionControl, mapper)
|
||||||
server := httptest.NewServer(handler)
|
server := httptest.NewServer(handler)
|
||||||
defer server.Close()
|
defer server.Close()
|
||||||
|
|
||||||
|
@ -31,7 +31,7 @@ func TestRedirect(t *testing.T) {
|
|||||||
}
|
}
|
||||||
handler := Handle(map[string]RESTStorage{
|
handler := Handle(map[string]RESTStorage{
|
||||||
"foo": simpleStorage,
|
"foo": simpleStorage,
|
||||||
}, codec, "/prefix", "version", selfLinker, admissionControl)
|
}, codec, "/prefix", "version", selfLinker, admissionControl, mapper)
|
||||||
server := httptest.NewServer(handler)
|
server := httptest.NewServer(handler)
|
||||||
defer server.Close()
|
defer server.Close()
|
||||||
|
|
||||||
@ -84,7 +84,7 @@ func TestRedirectWithNamespaces(t *testing.T) {
|
|||||||
}
|
}
|
||||||
handler := Handle(map[string]RESTStorage{
|
handler := Handle(map[string]RESTStorage{
|
||||||
"foo": simpleStorage,
|
"foo": simpleStorage,
|
||||||
}, codec, "/prefix", "version", selfLinker, admissionControl)
|
}, codec, "/prefix", "version", selfLinker, admissionControl, mapper)
|
||||||
server := httptest.NewServer(handler)
|
server := httptest.NewServer(handler)
|
||||||
defer server.Close()
|
defer server.Close()
|
||||||
|
|
||||||
|
@ -50,7 +50,7 @@ func TestWatchWebsocket(t *testing.T) {
|
|||||||
_ = ResourceWatcher(simpleStorage) // Give compile error if this doesn't work.
|
_ = ResourceWatcher(simpleStorage) // Give compile error if this doesn't work.
|
||||||
handler := Handle(map[string]RESTStorage{
|
handler := Handle(map[string]RESTStorage{
|
||||||
"foo": simpleStorage,
|
"foo": simpleStorage,
|
||||||
}, codec, "/api", "version", selfLinker, admissionControl)
|
}, codec, "/api", "version", selfLinker, admissionControl, mapper)
|
||||||
server := httptest.NewServer(handler)
|
server := httptest.NewServer(handler)
|
||||||
defer server.Close()
|
defer server.Close()
|
||||||
|
|
||||||
@ -104,7 +104,7 @@ func TestWatchHTTP(t *testing.T) {
|
|||||||
simpleStorage := &SimpleRESTStorage{}
|
simpleStorage := &SimpleRESTStorage{}
|
||||||
handler := Handle(map[string]RESTStorage{
|
handler := Handle(map[string]RESTStorage{
|
||||||
"foo": simpleStorage,
|
"foo": simpleStorage,
|
||||||
}, codec, "/api", "version", selfLinker, admissionControl)
|
}, codec, "/api", "version", selfLinker, admissionControl, mapper)
|
||||||
server := httptest.NewServer(handler)
|
server := httptest.NewServer(handler)
|
||||||
defer server.Close()
|
defer server.Close()
|
||||||
client := http.Client{}
|
client := http.Client{}
|
||||||
@ -167,7 +167,7 @@ func TestWatchParamParsing(t *testing.T) {
|
|||||||
simpleStorage := &SimpleRESTStorage{}
|
simpleStorage := &SimpleRESTStorage{}
|
||||||
handler := Handle(map[string]RESTStorage{
|
handler := Handle(map[string]RESTStorage{
|
||||||
"foo": simpleStorage,
|
"foo": simpleStorage,
|
||||||
}, codec, "/api", "version", selfLinker, admissionControl)
|
}, codec, "/api", "version", selfLinker, admissionControl, mapper)
|
||||||
server := httptest.NewServer(handler)
|
server := httptest.NewServer(handler)
|
||||||
defer server.Close()
|
defer server.Close()
|
||||||
|
|
||||||
@ -239,7 +239,7 @@ func TestWatchProtocolSelection(t *testing.T) {
|
|||||||
simpleStorage := &SimpleRESTStorage{}
|
simpleStorage := &SimpleRESTStorage{}
|
||||||
handler := Handle(map[string]RESTStorage{
|
handler := Handle(map[string]RESTStorage{
|
||||||
"foo": simpleStorage,
|
"foo": simpleStorage,
|
||||||
}, codec, "/api", "version", selfLinker, admissionControl)
|
}, codec, "/api", "version", selfLinker, admissionControl, mapper)
|
||||||
server := httptest.NewServer(handler)
|
server := httptest.NewServer(handler)
|
||||||
defer server.Close()
|
defer server.Close()
|
||||||
defer server.CloseClientConnections()
|
defer server.CloseClientConnections()
|
||||||
|
@ -66,7 +66,13 @@ func newExternalScheme() (*runtime.Scheme, meta.RESTMapper, runtime.Codec) {
|
|||||||
MetadataAccessor: meta.NewAccessor(),
|
MetadataAccessor: meta.NewAccessor(),
|
||||||
}, (version == "unlikelyversion")
|
}, (version == "unlikelyversion")
|
||||||
})
|
})
|
||||||
mapper.Add(scheme, false, "unlikelyversion")
|
for _, version := range []string{"unlikelyversion"} {
|
||||||
|
for kind := range scheme.KnownTypes(version) {
|
||||||
|
mixedCase := false
|
||||||
|
scope := meta.RESTScopeNamespace
|
||||||
|
mapper.Add(scope, kind, version, mixedCase)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return scheme, mapper, codec
|
return scheme, mapper, codec
|
||||||
}
|
}
|
||||||
|
@ -318,7 +318,11 @@ func (b *Builder) visitorResult() *Result {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return &Result{err: err}
|
return &Result{err: err}
|
||||||
}
|
}
|
||||||
visitors = append(visitors, NewSelector(client, mapping, b.namespace, b.selector))
|
selectorNamespace := b.namespace
|
||||||
|
if mapping.Scope.Name() != meta.RESTScopeNameNamespace {
|
||||||
|
selectorNamespace = ""
|
||||||
|
}
|
||||||
|
visitors = append(visitors, NewSelector(client, mapping, selectorNamespace, b.selector))
|
||||||
}
|
}
|
||||||
if b.continueOnError {
|
if b.continueOnError {
|
||||||
return &Result{visitor: EagerVisitorList(visitors), sources: visitors}
|
return &Result{visitor: EagerVisitorList(visitors), sources: visitors}
|
||||||
@ -337,14 +341,19 @@ func (b *Builder) visitorResult() *Result {
|
|||||||
if len(b.resources) > 1 {
|
if len(b.resources) > 1 {
|
||||||
return &Result{singular: true, err: fmt.Errorf("you must specify only one resource")}
|
return &Result{singular: true, err: fmt.Errorf("you must specify only one resource")}
|
||||||
}
|
}
|
||||||
if len(b.namespace) == 0 {
|
|
||||||
return &Result{singular: true, err: fmt.Errorf("namespace may not be empty when retrieving a resource by name")}
|
|
||||||
}
|
|
||||||
mappings, err := b.resourceMappings()
|
mappings, err := b.resourceMappings()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &Result{singular: true, err: err}
|
return &Result{singular: true, err: err}
|
||||||
}
|
}
|
||||||
client, err := b.mapper.ClientForMapping(mappings[0])
|
mapping := mappings[0]
|
||||||
|
if mapping.Scope.Name() != meta.RESTScopeNameNamespace {
|
||||||
|
b.namespace = ""
|
||||||
|
} else {
|
||||||
|
if len(b.namespace) == 0 {
|
||||||
|
return &Result{singular: true, err: fmt.Errorf("namespace may not be empty when retrieving a resource by name")}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
client, err := b.mapper.ClientForMapping(mapping)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &Result{singular: true, err: err}
|
return &Result{singular: true, err: err}
|
||||||
}
|
}
|
||||||
@ -402,6 +411,7 @@ func (b *Builder) Do() *Result {
|
|||||||
if b.requireNamespace {
|
if b.requireNamespace {
|
||||||
helpers = append(helpers, RequireNamespace(b.namespace))
|
helpers = append(helpers, RequireNamespace(b.namespace))
|
||||||
}
|
}
|
||||||
|
helpers = append(helpers, FilterNamespace())
|
||||||
r.visitor = NewDecoratedVisitor(r.visitor, helpers...)
|
r.visitor = NewDecoratedVisitor(r.visitor, helpers...)
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
@ -30,6 +30,7 @@ import (
|
|||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/meta"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/meta"
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/resource"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/util/errors"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/util/errors"
|
||||||
@ -173,6 +174,37 @@ func TestPathBuilder(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestNodeBuilder(t *testing.T) {
|
||||||
|
node := &api.Node{
|
||||||
|
ObjectMeta: api.ObjectMeta{Name: "node1", Namespace: "should-not-have", ResourceVersion: "10"},
|
||||||
|
Spec: api.NodeSpec{
|
||||||
|
Capacity: api.ResourceList{
|
||||||
|
api.ResourceCPU: resource.MustParse("1000m"),
|
||||||
|
api.ResourceMemory: resource.MustParse("1Mi"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
r, w := io.Pipe()
|
||||||
|
go func() {
|
||||||
|
defer w.Close()
|
||||||
|
w.Write([]byte(runtime.EncodeOrDie(latest.Codec, node)))
|
||||||
|
}()
|
||||||
|
|
||||||
|
b := NewBuilder(latest.RESTMapper, api.Scheme, fakeClient()).
|
||||||
|
NamespaceParam("test").Stream(r, "STDIN")
|
||||||
|
|
||||||
|
test := &testVisitor{}
|
||||||
|
|
||||||
|
err := b.Do().Visit(test.Handle)
|
||||||
|
if err != nil || len(test.Infos) != 1 {
|
||||||
|
t.Fatalf("unexpected response: %v %#v", err, test.Infos)
|
||||||
|
}
|
||||||
|
info := test.Infos[0]
|
||||||
|
if info.Name != "node1" || info.Namespace != "" || info.Object == nil {
|
||||||
|
t.Errorf("unexpected info: %#v", info)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestPathBuilderWithMultiple(t *testing.T) {
|
func TestPathBuilderWithMultiple(t *testing.T) {
|
||||||
b := NewBuilder(latest.RESTMapper, api.Scheme, fakeClient()).
|
b := NewBuilder(latest.RESTMapper, api.Scheme, fakeClient()).
|
||||||
FilenameParam("../../../examples/guestbook/redis-master.json").
|
FilenameParam("../../../examples/guestbook/redis-master.json").
|
||||||
|
@ -384,6 +384,17 @@ func UpdateObjectNamespace(info *Info) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FilterNamespace omits the namespace if the object is not namespace scoped
|
||||||
|
func FilterNamespace() VisitorFunc {
|
||||||
|
return func(info *Info) error {
|
||||||
|
if info.Mapping.Scope.Name() != meta.RESTScopeNameNamespace {
|
||||||
|
info.Namespace = ""
|
||||||
|
UpdateObjectNamespace(info)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// SetNamespace ensures that every Info object visited will have a namespace
|
// SetNamespace ensures that every Info object visited will have a namespace
|
||||||
// set. If info.Object is set, it will be mutated as well.
|
// set. If info.Object is set, it will be mutated as well.
|
||||||
func SetNamespace(namespace string) VisitorFunc {
|
func SetNamespace(namespace string) VisitorFunc {
|
||||||
|
@ -31,6 +31,7 @@ import (
|
|||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/admission"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/admission"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest"
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/meta"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/v1beta1"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/v1beta1"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/v1beta2"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/v1beta2"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/v1beta3"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/v1beta3"
|
||||||
@ -491,25 +492,25 @@ func (m *Master) getServersToValidate(c *Config) map[string]apiserver.Server {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// api_v1beta1 returns the resources and codec for API version v1beta1.
|
// api_v1beta1 returns the resources and codec for API version v1beta1.
|
||||||
func (m *Master) api_v1beta1() (map[string]apiserver.RESTStorage, runtime.Codec, string, runtime.SelfLinker, admission.Interface) {
|
func (m *Master) api_v1beta1() (map[string]apiserver.RESTStorage, runtime.Codec, string, runtime.SelfLinker, admission.Interface, meta.RESTMapper) {
|
||||||
storage := make(map[string]apiserver.RESTStorage)
|
storage := make(map[string]apiserver.RESTStorage)
|
||||||
for k, v := range m.storage {
|
for k, v := range m.storage {
|
||||||
storage[k] = v
|
storage[k] = v
|
||||||
}
|
}
|
||||||
return storage, v1beta1.Codec, "/api/v1beta1", latest.SelfLinker, m.admissionControl
|
return storage, v1beta1.Codec, "/api/v1beta1", latest.SelfLinker, m.admissionControl, latest.RESTMapper
|
||||||
}
|
}
|
||||||
|
|
||||||
// api_v1beta2 returns the resources and codec for API version v1beta2.
|
// api_v1beta2 returns the resources and codec for API version v1beta2.
|
||||||
func (m *Master) api_v1beta2() (map[string]apiserver.RESTStorage, runtime.Codec, string, runtime.SelfLinker, admission.Interface) {
|
func (m *Master) api_v1beta2() (map[string]apiserver.RESTStorage, runtime.Codec, string, runtime.SelfLinker, admission.Interface, meta.RESTMapper) {
|
||||||
storage := make(map[string]apiserver.RESTStorage)
|
storage := make(map[string]apiserver.RESTStorage)
|
||||||
for k, v := range m.storage {
|
for k, v := range m.storage {
|
||||||
storage[k] = v
|
storage[k] = v
|
||||||
}
|
}
|
||||||
return storage, v1beta2.Codec, "/api/v1beta2", latest.SelfLinker, m.admissionControl
|
return storage, v1beta2.Codec, "/api/v1beta2", latest.SelfLinker, m.admissionControl, latest.RESTMapper
|
||||||
}
|
}
|
||||||
|
|
||||||
// api_v1beta3 returns the resources and codec for API version v1beta3.
|
// api_v1beta3 returns the resources and codec for API version v1beta3.
|
||||||
func (m *Master) api_v1beta3() (map[string]apiserver.RESTStorage, runtime.Codec, string, runtime.SelfLinker, admission.Interface) {
|
func (m *Master) api_v1beta3() (map[string]apiserver.RESTStorage, runtime.Codec, string, runtime.SelfLinker, admission.Interface, meta.RESTMapper) {
|
||||||
storage := make(map[string]apiserver.RESTStorage)
|
storage := make(map[string]apiserver.RESTStorage)
|
||||||
for k, v := range m.storage {
|
for k, v := range m.storage {
|
||||||
if k == "minions" {
|
if k == "minions" {
|
||||||
@ -517,5 +518,5 @@ func (m *Master) api_v1beta3() (map[string]apiserver.RESTStorage, runtime.Codec,
|
|||||||
}
|
}
|
||||||
storage[strings.ToLower(k)] = v
|
storage[strings.ToLower(k)] = v
|
||||||
}
|
}
|
||||||
return storage, v1beta3.Codec, "/api/v1beta3", latest.SelfLinker, m.admissionControl
|
return storage, v1beta3.Codec, "/api/v1beta3", latest.SelfLinker, m.admissionControl, latest.RESTMapper
|
||||||
}
|
}
|
||||||
|
@ -245,10 +245,10 @@ func getTestRequests() []struct {
|
|||||||
{"GET", "/api/v1beta1/bindings", "", code405}, // Bindings are write-only
|
{"GET", "/api/v1beta1/bindings", "", code405}, // Bindings are write-only
|
||||||
{"POST", "/api/v1beta1/pods" + timeoutFlag, aPod, code200}, // Need a pod to bind or you get a 404
|
{"POST", "/api/v1beta1/pods" + timeoutFlag, aPod, code200}, // Need a pod to bind or you get a 404
|
||||||
{"POST", "/api/v1beta1/bindings" + timeoutFlag, aBinding, code200},
|
{"POST", "/api/v1beta1/bindings" + timeoutFlag, aBinding, code200},
|
||||||
{"PUT", "/api/v1beta1/bindings/a" + timeoutFlag, aBinding, code405},
|
{"PUT", "/api/v1beta1/bindings/a" + timeoutFlag, aBinding, code404},
|
||||||
{"GET", "/api/v1beta1/bindings", "", code405},
|
{"GET", "/api/v1beta1/bindings", "", code405},
|
||||||
{"GET", "/api/v1beta1/bindings/a", "", code405}, // No bindings instances
|
{"GET", "/api/v1beta1/bindings/a", "", code404}, // No bindings instances
|
||||||
{"DELETE", "/api/v1beta1/bindings/a" + timeoutFlag, "", code405},
|
{"DELETE", "/api/v1beta1/bindings/a" + timeoutFlag, "", code404},
|
||||||
|
|
||||||
// Non-existent object type.
|
// Non-existent object type.
|
||||||
{"GET", "/api/v1beta1/foo", "", code404},
|
{"GET", "/api/v1beta1/foo", "", code404},
|
||||||
|
Loading…
Reference in New Issue
Block a user