mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-30 23:15:14 +00:00
Merge pull request #7718 from csrwng/create_sub_name
API Server - pass path name in context of create request for subresource
This commit is contained in:
commit
b60a90c3f4
@ -28,6 +28,25 @@ import (
|
|||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/watch"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/watch"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
//TODO:
|
||||||
|
// Storage interfaces need to be separated into two groups; those that operate
|
||||||
|
// on collections and those that operate on individually named items.
|
||||||
|
// Collection interfaces:
|
||||||
|
// (Method: Current -> Proposed)
|
||||||
|
// GET: Lister -> CollectionGetter
|
||||||
|
// WATCH: Watcher -> CollectionWatcher
|
||||||
|
// CREATE: Creater -> CollectionCreater
|
||||||
|
// DELETE: (n/a) -> CollectionDeleter
|
||||||
|
// UPDATE: (n/a) -> CollectionUpdater
|
||||||
|
//
|
||||||
|
// Single item interfaces:
|
||||||
|
// (Method: Current -> Proposed)
|
||||||
|
// GET: Getter -> NamedGetter
|
||||||
|
// WATCH: (n/a) -> NamedWatcher
|
||||||
|
// CREATE: (n/a) -> NamedCreater
|
||||||
|
// DELETE: Deleter -> NamedDeleter
|
||||||
|
// UPDATE: Update -> NamedUpdater
|
||||||
|
|
||||||
// Storage is a generic interface for RESTful storage services.
|
// Storage is a generic interface for RESTful storage services.
|
||||||
// Resources which are exported to the RESTful API of apiserver need to implement this interface. It is expected
|
// Resources which are exported to the RESTful API of apiserver need to implement this interface. It is expected
|
||||||
// that objects may implement any of the below interfaces.
|
// that objects may implement any of the below interfaces.
|
||||||
@ -117,6 +136,18 @@ type Creater interface {
|
|||||||
Create(ctx api.Context, obj runtime.Object) (runtime.Object, error)
|
Create(ctx api.Context, obj runtime.Object) (runtime.Object, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NamedCreater is an object that can create an instance of a RESTful object using a name parameter.
|
||||||
|
type NamedCreater interface {
|
||||||
|
// New returns an empty object that can be used with Create after request data has been put into it.
|
||||||
|
// This object must be a pointer type for use with Codec.DecodeInto([]byte, runtime.Object)
|
||||||
|
New() runtime.Object
|
||||||
|
|
||||||
|
// Create creates a new version of a resource. It expects a name parameter from the path.
|
||||||
|
// This is needed for create operations on subresources which include the name of the parent
|
||||||
|
// resource in the path.
|
||||||
|
Create(ctx api.Context, name string, obj runtime.Object) (runtime.Object, error)
|
||||||
|
}
|
||||||
|
|
||||||
// Updater is an object that can update an instance of a RESTful object.
|
// Updater is an object that can update an instance of a RESTful object.
|
||||||
type Updater interface {
|
type Updater interface {
|
||||||
// New returns an empty object that can be used with Update after request data has been put into it.
|
// New returns an empty object that can be used with Update after request data has been put into it.
|
||||||
|
@ -130,6 +130,7 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
|
|||||||
|
|
||||||
// what verbs are supported by the storage, used to know what verbs we support per path
|
// what verbs are supported by the storage, used to know what verbs we support per path
|
||||||
creater, isCreater := storage.(rest.Creater)
|
creater, isCreater := storage.(rest.Creater)
|
||||||
|
namedCreater, isNamedCreater := storage.(rest.NamedCreater)
|
||||||
lister, isLister := storage.(rest.Lister)
|
lister, isLister := storage.(rest.Lister)
|
||||||
getter, isGetter := storage.(rest.Getter)
|
getter, isGetter := storage.(rest.Getter)
|
||||||
getterWithOptions, isGetterWithOptions := storage.(rest.GetterWithOptions)
|
getterWithOptions, isGetterWithOptions := storage.(rest.GetterWithOptions)
|
||||||
@ -145,6 +146,10 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
|
|||||||
storageMeta = defaultStorageMetadata{}
|
storageMeta = defaultStorageMetadata{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if isNamedCreater {
|
||||||
|
isCreater = true
|
||||||
|
}
|
||||||
|
|
||||||
var versionedList interface{}
|
var versionedList interface{}
|
||||||
if isLister {
|
if isLister {
|
||||||
list := lister.NewList()
|
list := lister.NewList()
|
||||||
@ -436,7 +441,13 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
|
|||||||
addParams(route, action.Params)
|
addParams(route, action.Params)
|
||||||
ws.Route(route)
|
ws.Route(route)
|
||||||
case "POST": // Create a resource.
|
case "POST": // Create a resource.
|
||||||
route := ws.POST(action.Path).To(CreateResource(creater, reqScope, a.group.Typer, admit)).
|
var handler restful.RouteFunction
|
||||||
|
if isNamedCreater {
|
||||||
|
handler = CreateNamedResource(namedCreater, reqScope, a.group.Typer, admit)
|
||||||
|
} else {
|
||||||
|
handler = CreateResource(creater, reqScope, a.group.Typer, admit)
|
||||||
|
}
|
||||||
|
route := ws.POST(action.Path).To(handler).
|
||||||
Filter(m).
|
Filter(m).
|
||||||
Doc("create a "+kind).
|
Doc("create a "+kind).
|
||||||
Operation("create"+kind).
|
Operation("create"+kind).
|
||||||
|
@ -531,6 +531,25 @@ func (r *GetWithOptionsRESTStorage) NewGetOptions() (runtime.Object, bool, strin
|
|||||||
|
|
||||||
var _ rest.GetterWithOptions = &GetWithOptionsRESTStorage{}
|
var _ rest.GetterWithOptions = &GetWithOptionsRESTStorage{}
|
||||||
|
|
||||||
|
type NamedCreaterRESTStorage struct {
|
||||||
|
*SimpleRESTStorage
|
||||||
|
createdName string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (storage *NamedCreaterRESTStorage) Create(ctx api.Context, name string, obj runtime.Object) (runtime.Object, error) {
|
||||||
|
storage.checkContext(ctx)
|
||||||
|
storage.created = obj.(*Simple)
|
||||||
|
storage.createdName = name
|
||||||
|
if err := storage.errors["create"]; err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var err error
|
||||||
|
if storage.injectedFunction != nil {
|
||||||
|
obj, err = storage.injectedFunction(obj)
|
||||||
|
}
|
||||||
|
return obj, err
|
||||||
|
}
|
||||||
|
|
||||||
func extractBody(response *http.Response, object runtime.Object) (string, error) {
|
func extractBody(response *http.Response, object runtime.Object) (string, error) {
|
||||||
defer response.Body.Close()
|
defer response.Body.Close()
|
||||||
body, err := ioutil.ReadAll(response.Body)
|
body, err := ioutil.ReadAll(response.Body)
|
||||||
@ -1810,6 +1829,32 @@ func TestCreateChecksDecode(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCreateWithName(t *testing.T) {
|
||||||
|
pathName := "helloworld"
|
||||||
|
storage := &NamedCreaterRESTStorage{SimpleRESTStorage: &SimpleRESTStorage{}}
|
||||||
|
handler := handle(map[string]rest.Storage{"simple/sub": storage})
|
||||||
|
server := httptest.NewServer(handler)
|
||||||
|
defer server.Close()
|
||||||
|
client := http.Client{}
|
||||||
|
|
||||||
|
simple := &Simple{Other: "foo"}
|
||||||
|
data, _ := codec.Encode(simple)
|
||||||
|
request, err := http.NewRequest("POST", server.URL+"/api/version/simple/"+pathName+"/sub", bytes.NewBuffer(data))
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
response, err := client.Do(request)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
if response.StatusCode != http.StatusCreated {
|
||||||
|
t.Errorf("Unexpected response %#v", response)
|
||||||
|
}
|
||||||
|
if storage.createdName != pathName {
|
||||||
|
t.Errorf("Did not get expected name in create context. Got: %s, Expected: %s", storage.createdName, pathName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestUpdateChecksDecode(t *testing.T) {
|
func TestUpdateChecksDecode(t *testing.T) {
|
||||||
handler := handle(map[string]rest.Storage{"simple": &SimpleRESTStorage{}})
|
handler := handle(map[string]rest.Storage{"simple": &SimpleRESTStorage{}})
|
||||||
server := httptest.NewServer(handler)
|
server := httptest.NewServer(handler)
|
||||||
|
@ -256,19 +256,27 @@ func ListResource(r rest.Lister, rw rest.Watcher, scope RequestScope, forceWatch
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateResource returns a function that will handle a resource creation.
|
func createHandler(r rest.NamedCreater, scope RequestScope, typer runtime.ObjectTyper, admit admission.Interface, includeName bool) restful.RouteFunction {
|
||||||
func CreateResource(r rest.Creater, scope RequestScope, typer runtime.ObjectTyper, admit admission.Interface) restful.RouteFunction {
|
|
||||||
return func(req *restful.Request, res *restful.Response) {
|
return func(req *restful.Request, res *restful.Response) {
|
||||||
w := res.ResponseWriter
|
w := res.ResponseWriter
|
||||||
|
|
||||||
// TODO: we either want to remove timeout or document it (if we document, move timeout out of this function and declare it in api_installer)
|
// TODO: we either want to remove timeout or document it (if we document, move timeout out of this function and declare it in api_installer)
|
||||||
timeout := parseTimeout(req.Request.URL.Query().Get("timeout"))
|
timeout := parseTimeout(req.Request.URL.Query().Get("timeout"))
|
||||||
|
|
||||||
namespace, err := scope.Namer.Namespace(req)
|
var (
|
||||||
|
namespace, name string
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
if includeName {
|
||||||
|
namespace, name, err = scope.Namer.Name(req)
|
||||||
|
} else {
|
||||||
|
namespace, err = scope.Namer.Namespace(req)
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errorJSON(err, scope.Codec, w)
|
errorJSON(err, scope.Codec, w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx := scope.ContextFunc(req)
|
ctx := scope.ContextFunc(req)
|
||||||
ctx = api.WithNamespace(ctx, namespace)
|
ctx = api.WithNamespace(ctx, namespace)
|
||||||
|
|
||||||
@ -292,7 +300,7 @@ func CreateResource(r rest.Creater, scope RequestScope, typer runtime.ObjectType
|
|||||||
}
|
}
|
||||||
|
|
||||||
result, err := finishRequest(timeout, func() (runtime.Object, error) {
|
result, err := finishRequest(timeout, func() (runtime.Object, error) {
|
||||||
out, err := r.Create(ctx, obj)
|
out, err := r.Create(ctx, name, obj)
|
||||||
if status, ok := out.(*api.Status); ok && err == nil && status.Code == 0 {
|
if status, ok := out.(*api.Status); ok && err == nil && status.Code == 0 {
|
||||||
status.Code = http.StatusCreated
|
status.Code = http.StatusCreated
|
||||||
}
|
}
|
||||||
@ -312,6 +320,24 @@ func CreateResource(r rest.Creater, scope RequestScope, typer runtime.ObjectType
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CreateNamedResource returns a function that will handle a resource creation with name.
|
||||||
|
func CreateNamedResource(r rest.NamedCreater, scope RequestScope, typer runtime.ObjectTyper, admit admission.Interface) restful.RouteFunction {
|
||||||
|
return createHandler(r, scope, typer, admit, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateResource returns a function that will handle a resource creation.
|
||||||
|
func CreateResource(r rest.Creater, scope RequestScope, typer runtime.ObjectTyper, admit admission.Interface) restful.RouteFunction {
|
||||||
|
return createHandler(&namedCreaterAdapter{r}, scope, typer, admit, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
type namedCreaterAdapter struct {
|
||||||
|
rest.Creater
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *namedCreaterAdapter) Create(ctx api.Context, name string, obj runtime.Object) (runtime.Object, error) {
|
||||||
|
return c.Creater.Create(ctx, obj)
|
||||||
|
}
|
||||||
|
|
||||||
// PatchResource returns a function that will handle a resource patch
|
// PatchResource returns a function that will handle a resource patch
|
||||||
// TODO: Eventually PatchResource should just use GuaranteedUpdate and this routine should be a bit cleaner
|
// TODO: Eventually PatchResource should just use GuaranteedUpdate and this routine should be a bit cleaner
|
||||||
func PatchResource(r rest.Patcher, scope RequestScope, typer runtime.ObjectTyper, admit admission.Interface, converter runtime.ObjectConvertor) restful.RouteFunction {
|
func PatchResource(r rest.Patcher, scope RequestScope, typer runtime.ObjectTyper, admit admission.Interface, converter runtime.ObjectConvertor) restful.RouteFunction {
|
||||||
|
Loading…
Reference in New Issue
Block a user