mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-29 06:27:05 +00:00
Allow InputStreams to be returned by requests
Add additional metadata to restful services.
This commit is contained in:
parent
ea32b89e5e
commit
fc3609fb5b
@ -17,6 +17,7 @@ limitations under the License.
|
|||||||
package rest
|
package rest
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
|
||||||
@ -148,3 +149,21 @@ type Redirector interface {
|
|||||||
// ResourceLocation should return the remote location of the given resource, and an optional transport to use to request it, or an error.
|
// ResourceLocation should return the remote location of the given resource, and an optional transport to use to request it, or an error.
|
||||||
ResourceLocation(ctx api.Context, id string) (remoteLocation *url.URL, transport http.RoundTripper, err error)
|
ResourceLocation(ctx api.Context, id string) (remoteLocation *url.URL, transport http.RoundTripper, err error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ResourceStreamer is an interface implemented by objects that prefer to be streamed from the server
|
||||||
|
// instead of decoded directly.
|
||||||
|
type ResourceStreamer interface {
|
||||||
|
// InputStream should return an io.Reader if the provided object supports streaming. The desired
|
||||||
|
// api version and a accept header (may be empty) are passed to the call. If no error occurs,
|
||||||
|
// the caller may return a content type string with the reader that indicates the type of the
|
||||||
|
// stream.
|
||||||
|
InputStream(apiVersion, acceptHeader string) (io.ReadCloser, string, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// StorageMetadata is an optional interface that callers can implement to provide additional
|
||||||
|
// information about their Storage objects.
|
||||||
|
type StorageMetadata interface {
|
||||||
|
// ProducesMIMETypes returns a list of the MIME types the specified HTTP verb (GET, POST, DELETE,
|
||||||
|
// PATCH) can respond with.
|
||||||
|
ProducesMIMETypes(verb string) []string
|
||||||
|
}
|
||||||
|
@ -147,6 +147,10 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
|
|||||||
patcher, isPatcher := storage.(rest.Patcher)
|
patcher, isPatcher := storage.(rest.Patcher)
|
||||||
_, isWatcher := storage.(rest.Watcher)
|
_, isWatcher := storage.(rest.Watcher)
|
||||||
_, isRedirector := storage.(rest.Redirector)
|
_, isRedirector := storage.(rest.Redirector)
|
||||||
|
storageMeta, isMetadata := storage.(rest.StorageMetadata)
|
||||||
|
if !isMetadata {
|
||||||
|
storageMeta = defaultStorageMetadata{}
|
||||||
|
}
|
||||||
|
|
||||||
var versionedDeleterObject runtime.Object
|
var versionedDeleterObject runtime.Object
|
||||||
switch {
|
switch {
|
||||||
@ -283,56 +287,70 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
|
|||||||
//
|
//
|
||||||
// test/integration/auth_test.go is currently the most comprehensive status code test
|
// test/integration/auth_test.go is currently the most comprehensive status code test
|
||||||
|
|
||||||
|
reqScope := RequestScope{
|
||||||
|
ContextFunc: ctxFn,
|
||||||
|
Codec: mapping.Codec,
|
||||||
|
APIVersion: a.group.Version,
|
||||||
|
Resource: resource,
|
||||||
|
Kind: kind,
|
||||||
|
}
|
||||||
for _, action := range actions {
|
for _, action := range actions {
|
||||||
|
reqScope.Namer = action.Namer
|
||||||
m := monitorFilter(action.Verb, resource)
|
m := monitorFilter(action.Verb, resource)
|
||||||
switch action.Verb {
|
switch action.Verb {
|
||||||
case "GET": // Get a resource.
|
case "GET": // Get a resource.
|
||||||
route := ws.GET(action.Path).To(GetResource(getter, ctxFn, action.Namer, mapping.Codec)).
|
route := ws.GET(action.Path).To(GetResource(getter, reqScope)).
|
||||||
Filter(m).
|
Filter(m).
|
||||||
Doc("read the specified " + kind).
|
Doc("read the specified " + kind).
|
||||||
Operation("read" + kind).
|
Operation("read" + kind).
|
||||||
|
Produces(append(storageMeta.ProducesMIMETypes(action.Verb), "application/json")...).
|
||||||
Writes(versionedObject)
|
Writes(versionedObject)
|
||||||
addParams(route, action.Params)
|
addParams(route, action.Params)
|
||||||
ws.Route(route)
|
ws.Route(route)
|
||||||
case "LIST": // List all resources of a kind.
|
case "LIST": // List all resources of a kind.
|
||||||
route := ws.GET(action.Path).To(ListResource(lister, ctxFn, action.Namer, mapping.Codec, a.group.Version, resource)).
|
route := ws.GET(action.Path).To(ListResource(lister, reqScope)).
|
||||||
Filter(m).
|
Filter(m).
|
||||||
Doc("list objects of kind " + kind).
|
Doc("list objects of kind " + kind).
|
||||||
Operation("list" + kind).
|
Operation("list" + kind).
|
||||||
|
Produces("application/json").
|
||||||
Writes(versionedList)
|
Writes(versionedList)
|
||||||
addParams(route, action.Params)
|
addParams(route, action.Params)
|
||||||
ws.Route(route)
|
ws.Route(route)
|
||||||
case "PUT": // Update a resource.
|
case "PUT": // Update a resource.
|
||||||
route := ws.PUT(action.Path).To(UpdateResource(updater, ctxFn, action.Namer, mapping.Codec, a.group.Typer, resource, admit)).
|
route := ws.PUT(action.Path).To(UpdateResource(updater, reqScope, a.group.Typer, admit)).
|
||||||
Filter(m).
|
Filter(m).
|
||||||
Doc("replace the specified " + kind).
|
Doc("replace the specified " + kind).
|
||||||
Operation("replace" + kind).
|
Operation("replace" + kind).
|
||||||
|
Produces(append(storageMeta.ProducesMIMETypes(action.Verb), "application/json")...).
|
||||||
Reads(versionedObject)
|
Reads(versionedObject)
|
||||||
addParams(route, action.Params)
|
addParams(route, action.Params)
|
||||||
ws.Route(route)
|
ws.Route(route)
|
||||||
case "PATCH": // Partially update a resource
|
case "PATCH": // Partially update a resource
|
||||||
route := ws.PATCH(action.Path).To(PatchResource(patcher, ctxFn, action.Namer, mapping.Codec, a.group.Typer, resource, admit)).
|
route := ws.PATCH(action.Path).To(PatchResource(patcher, reqScope, a.group.Typer, admit)).
|
||||||
Filter(m).
|
Filter(m).
|
||||||
Doc("partially update the specified " + kind).
|
Doc("partially update the specified " + kind).
|
||||||
// TODO: toggle patch strategy by content type
|
// TODO: toggle patch strategy by content type
|
||||||
// Consumes("application/merge-patch+json", "application/json-patch+json").
|
// Consumes("application/merge-patch+json", "application/json-patch+json").
|
||||||
Operation("patch" + kind).
|
Operation("patch" + kind).
|
||||||
|
Produces(append(storageMeta.ProducesMIMETypes(action.Verb), "application/json")...).
|
||||||
Reads(versionedObject)
|
Reads(versionedObject)
|
||||||
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, ctxFn, action.Namer, mapping.Codec, a.group.Typer, resource, admit)).
|
route := ws.POST(action.Path).To(CreateResource(creater, reqScope, a.group.Typer, admit)).
|
||||||
Filter(m).
|
Filter(m).
|
||||||
Doc("create a " + kind).
|
Doc("create a " + kind).
|
||||||
Operation("create" + kind).
|
Operation("create" + kind).
|
||||||
|
Produces(append(storageMeta.ProducesMIMETypes(action.Verb), "application/json")...).
|
||||||
Reads(versionedObject)
|
Reads(versionedObject)
|
||||||
addParams(route, action.Params)
|
addParams(route, action.Params)
|
||||||
ws.Route(route)
|
ws.Route(route)
|
||||||
case "DELETE": // Delete a resource.
|
case "DELETE": // Delete a resource.
|
||||||
route := ws.DELETE(action.Path).To(DeleteResource(gracefulDeleter, isGracefulDeleter, ctxFn, action.Namer, mapping.Codec, resource, kind, admit)).
|
route := ws.DELETE(action.Path).To(DeleteResource(gracefulDeleter, isGracefulDeleter, reqScope, admit)).
|
||||||
Filter(m).
|
Filter(m).
|
||||||
Doc("delete a " + kind).
|
Doc("delete a " + kind).
|
||||||
Operation("delete" + kind)
|
Operation("delete" + kind).
|
||||||
|
Produces(append(storageMeta.ProducesMIMETypes(action.Verb), "application/json")...)
|
||||||
if isGracefulDeleter {
|
if isGracefulDeleter {
|
||||||
route.Reads(versionedDeleterObject)
|
route.Reads(versionedDeleterObject)
|
||||||
}
|
}
|
||||||
@ -343,6 +361,7 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
|
|||||||
Filter(m).
|
Filter(m).
|
||||||
Doc("watch a particular " + kind).
|
Doc("watch a particular " + kind).
|
||||||
Operation("watch" + kind).
|
Operation("watch" + kind).
|
||||||
|
Produces("application/json").
|
||||||
Writes(versionedObject)
|
Writes(versionedObject)
|
||||||
addParams(route, action.Params)
|
addParams(route, action.Params)
|
||||||
ws.Route(route)
|
ws.Route(route)
|
||||||
@ -351,6 +370,7 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
|
|||||||
Filter(m).
|
Filter(m).
|
||||||
Doc("watch a list of " + kind).
|
Doc("watch a list of " + kind).
|
||||||
Operation("watch" + kind + "list").
|
Operation("watch" + kind + "list").
|
||||||
|
Produces("application/json").
|
||||||
Writes(versionedList)
|
Writes(versionedList)
|
||||||
addParams(route, action.Params)
|
addParams(route, action.Params)
|
||||||
ws.Route(route)
|
ws.Route(route)
|
||||||
@ -630,3 +650,13 @@ func addParams(route *restful.RouteBuilder, params []*restful.Parameter) {
|
|||||||
route.Param(param)
|
route.Param(param)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
@ -20,6 +20,7 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
"path"
|
"path"
|
||||||
@ -196,6 +197,26 @@ func APIVersionHandler(versions ...string) restful.RouteFunction {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// write renders a returned runtime.Object to the response as a stream or an encoded object.
|
||||||
|
func write(statusCode int, apiVersion string, codec runtime.Codec, object runtime.Object, w http.ResponseWriter, req *http.Request) {
|
||||||
|
if stream, ok := object.(rest.ResourceStreamer); ok {
|
||||||
|
out, contentType, err := stream.InputStream(apiVersion, req.Header.Get("Accept"))
|
||||||
|
if err != nil {
|
||||||
|
errorJSONFatal(err, codec, w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer out.Close()
|
||||||
|
if len(contentType) == 0 {
|
||||||
|
contentType = "application/octet-stream"
|
||||||
|
}
|
||||||
|
w.Header().Set("Content-Type", contentType)
|
||||||
|
w.WriteHeader(statusCode)
|
||||||
|
io.Copy(w, out)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
writeJSON(statusCode, codec, object, w)
|
||||||
|
}
|
||||||
|
|
||||||
// writeJSON renders an object as JSON to the response.
|
// writeJSON renders an object as JSON to the response.
|
||||||
func writeJSON(statusCode int, codec runtime.Codec, object runtime.Object, w http.ResponseWriter) {
|
func writeJSON(statusCode int, codec runtime.Codec, object runtime.Object, w http.ResponseWriter) {
|
||||||
output, err := codec.Encode(object)
|
output, err := codec.Encode(object)
|
||||||
|
@ -21,6 +21,7 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
@ -123,6 +124,7 @@ func init() {
|
|||||||
type defaultAPIServer struct {
|
type defaultAPIServer struct {
|
||||||
http.Handler
|
http.Handler
|
||||||
group *APIGroupVersion
|
group *APIGroupVersion
|
||||||
|
container *restful.Container
|
||||||
}
|
}
|
||||||
|
|
||||||
// uses the default settings
|
// uses the default settings
|
||||||
@ -169,7 +171,7 @@ func handleInternal(storage map[string]rest.Storage, admissionControl admission.
|
|||||||
ws := new(restful.WebService)
|
ws := new(restful.WebService)
|
||||||
InstallSupport(mux, ws)
|
InstallSupport(mux, ws)
|
||||||
container.Add(ws)
|
container.Add(ws)
|
||||||
return &defaultAPIServer{mux, group}
|
return &defaultAPIServer{mux, group, container}
|
||||||
}
|
}
|
||||||
|
|
||||||
type Simple struct {
|
type Simple struct {
|
||||||
@ -212,6 +214,8 @@ type SimpleRESTStorage struct {
|
|||||||
updated *Simple
|
updated *Simple
|
||||||
created *Simple
|
created *Simple
|
||||||
|
|
||||||
|
stream *SimpleStream
|
||||||
|
|
||||||
deleted string
|
deleted string
|
||||||
deleteOptions *api.DeleteOptions
|
deleteOptions *api.DeleteOptions
|
||||||
|
|
||||||
@ -243,8 +247,34 @@ func (storage *SimpleRESTStorage) List(ctx api.Context, label labels.Selector, f
|
|||||||
return result, storage.errors["list"]
|
return result, storage.errors["list"]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type SimpleStream struct {
|
||||||
|
version string
|
||||||
|
accept string
|
||||||
|
contentType string
|
||||||
|
err error
|
||||||
|
|
||||||
|
io.Reader
|
||||||
|
closed bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SimpleStream) Close() error {
|
||||||
|
s.closed = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SimpleStream) IsAnAPIObject() {}
|
||||||
|
|
||||||
|
func (s *SimpleStream) InputStream(version, accept string) (io.ReadCloser, string, error) {
|
||||||
|
s.version = version
|
||||||
|
s.accept = accept
|
||||||
|
return s, s.contentType, s.err
|
||||||
|
}
|
||||||
|
|
||||||
func (storage *SimpleRESTStorage) Get(ctx api.Context, id string) (runtime.Object, error) {
|
func (storage *SimpleRESTStorage) Get(ctx api.Context, id string) (runtime.Object, error) {
|
||||||
storage.checkContext(ctx)
|
storage.checkContext(ctx)
|
||||||
|
if id == "binary" {
|
||||||
|
return storage.stream, storage.errors["get"]
|
||||||
|
}
|
||||||
return api.Scheme.CopyOrDie(&storage.item), storage.errors["get"]
|
return api.Scheme.CopyOrDie(&storage.item), storage.errors["get"]
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -343,6 +373,15 @@ func (storage LegacyRESTStorage) Delete(ctx api.Context, id string) (runtime.Obj
|
|||||||
return storage.SimpleRESTStorage.Delete(ctx, id, nil)
|
return storage.SimpleRESTStorage.Delete(ctx, id, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type MetadataRESTStorage struct {
|
||||||
|
*SimpleRESTStorage
|
||||||
|
types []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MetadataRESTStorage) ProducesMIMETypes(method string) []string {
|
||||||
|
return m.types
|
||||||
|
}
|
||||||
|
|
||||||
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)
|
||||||
@ -646,6 +685,26 @@ func TestSelfLinkSkipsEmptyName(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestMetadata(t *testing.T) {
|
||||||
|
simpleStorage := &MetadataRESTStorage{&SimpleRESTStorage{}, []string{"text/plain"}}
|
||||||
|
h := handle(map[string]rest.Storage{"simple": simpleStorage})
|
||||||
|
ws := h.(*defaultAPIServer).container.RegisteredWebServices()
|
||||||
|
if len(ws) == 0 {
|
||||||
|
t.Fatal("no web services registered")
|
||||||
|
}
|
||||||
|
matches := map[string]int{}
|
||||||
|
for _, w := range ws {
|
||||||
|
for _, r := range w.Routes() {
|
||||||
|
s := strings.Join(r.Produces, ",")
|
||||||
|
i := matches[s]
|
||||||
|
matches[s] = i + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if matches["text/plain,application/json"] == 0 || matches["application/json"] == 0 || matches["*/*"] == 0 || len(matches) != 3 {
|
||||||
|
t.Errorf("unexpected mime types: %v", matches)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestGet(t *testing.T) {
|
func TestGet(t *testing.T) {
|
||||||
storage := map[string]rest.Storage{}
|
storage := map[string]rest.Storage{}
|
||||||
simpleStorage := SimpleRESTStorage{
|
simpleStorage := SimpleRESTStorage{
|
||||||
@ -685,6 +744,36 @@ func TestGet(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGetBinary(t *testing.T) {
|
||||||
|
simpleStorage := SimpleRESTStorage{
|
||||||
|
stream: &SimpleStream{
|
||||||
|
contentType: "text/plain",
|
||||||
|
Reader: bytes.NewBufferString("response data"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
stream := simpleStorage.stream
|
||||||
|
server := httptest.NewServer(handle(map[string]rest.Storage{"simple": &simpleStorage}))
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
req, _ := http.NewRequest("GET", server.URL+"/api/version/simple/binary", nil)
|
||||||
|
req.Header.Add("Accept", "text/other, */*")
|
||||||
|
resp, err := http.DefaultClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
t.Fatalf("unexpected response: %#v", resp)
|
||||||
|
}
|
||||||
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
if !stream.closed || stream.version != "version" || stream.accept != "text/other, */*" ||
|
||||||
|
resp.Header.Get("Content-Type") != stream.contentType || string(body) != "response data" {
|
||||||
|
t.Errorf("unexpected stream: %#v", stream)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestGetAlternateSelfLink(t *testing.T) {
|
func TestGetAlternateSelfLink(t *testing.T) {
|
||||||
storage := map[string]rest.Storage{}
|
storage := map[string]rest.Storage{}
|
||||||
simpleStorage := SimpleRESTStorage{
|
simpleStorage := SimpleRESTStorage{
|
||||||
|
@ -59,28 +59,38 @@ type ScopeNamer interface {
|
|||||||
GenerateListLink(req *restful.Request) (path, query string, err error)
|
GenerateListLink(req *restful.Request) (path, query string, err error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RequestScope encapsulates common fields across all RESTful handler methods.
|
||||||
|
type RequestScope struct {
|
||||||
|
Namer ScopeNamer
|
||||||
|
ContextFunc
|
||||||
|
runtime.Codec
|
||||||
|
Resource string
|
||||||
|
Kind string
|
||||||
|
APIVersion string
|
||||||
|
}
|
||||||
|
|
||||||
// GetResource returns a function that handles retrieving a single resource from a rest.Storage object.
|
// GetResource returns a function that handles retrieving a single resource from a rest.Storage object.
|
||||||
func GetResource(r rest.Getter, ctxFn ContextFunc, namer ScopeNamer, codec runtime.Codec) restful.RouteFunction {
|
func GetResource(r rest.Getter, scope RequestScope) restful.RouteFunction {
|
||||||
return func(req *restful.Request, res *restful.Response) {
|
return func(req *restful.Request, res *restful.Response) {
|
||||||
w := res.ResponseWriter
|
w := res.ResponseWriter
|
||||||
namespace, name, err := namer.Name(req)
|
namespace, name, err := scope.Namer.Name(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errorJSON(err, codec, w)
|
errorJSON(err, scope.Codec, w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
ctx := ctxFn(req)
|
ctx := scope.ContextFunc(req)
|
||||||
ctx = api.WithNamespace(ctx, namespace)
|
ctx = api.WithNamespace(ctx, namespace)
|
||||||
|
|
||||||
result, err := r.Get(ctx, name)
|
result, err := r.Get(ctx, name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errorJSON(err, codec, w)
|
errorJSON(err, scope.Codec, w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err := setSelfLink(result, req, namer); err != nil {
|
if err := setSelfLink(result, req, scope.Namer); err != nil {
|
||||||
errorJSON(err, codec, w)
|
errorJSON(err, scope.Codec, w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
writeJSON(http.StatusOK, codec, result, w)
|
write(http.StatusOK, scope.APIVersion, scope.Codec, result, w, req.Request)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -103,69 +113,69 @@ func parseSelectorQueryParams(query url.Values, version, apiResource string) (la
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ListResource returns a function that handles retrieving a list of resources from a rest.Storage object.
|
// ListResource returns a function that handles retrieving a list of resources from a rest.Storage object.
|
||||||
func ListResource(r rest.Lister, ctxFn ContextFunc, namer ScopeNamer, codec runtime.Codec, version, apiResource string) restful.RouteFunction {
|
func ListResource(r rest.Lister, scope RequestScope) restful.RouteFunction {
|
||||||
return func(req *restful.Request, res *restful.Response) {
|
return func(req *restful.Request, res *restful.Response) {
|
||||||
w := res.ResponseWriter
|
w := res.ResponseWriter
|
||||||
|
|
||||||
namespace, err := namer.Namespace(req)
|
namespace, err := scope.Namer.Namespace(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errorJSON(err, codec, w)
|
errorJSON(err, scope.Codec, w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
ctx := ctxFn(req)
|
ctx := scope.ContextFunc(req)
|
||||||
ctx = api.WithNamespace(ctx, namespace)
|
ctx = api.WithNamespace(ctx, namespace)
|
||||||
|
|
||||||
label, field, err := parseSelectorQueryParams(req.Request.URL.Query(), version, apiResource)
|
label, field, err := parseSelectorQueryParams(req.Request.URL.Query(), scope.APIVersion, scope.Resource)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errorJSON(err, codec, w)
|
errorJSON(err, scope.Codec, w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
result, err := r.List(ctx, label, field)
|
result, err := r.List(ctx, label, field)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errorJSON(err, codec, w)
|
errorJSON(err, scope.Codec, w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err := setListSelfLink(result, req, namer); err != nil {
|
if err := setListSelfLink(result, req, scope.Namer); err != nil {
|
||||||
errorJSON(err, codec, w)
|
errorJSON(err, scope.Codec, w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
writeJSON(http.StatusOK, codec, result, w)
|
write(http.StatusOK, scope.APIVersion, scope.Codec, result, w, req.Request)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateResource returns a function that will handle a resource creation.
|
// CreateResource returns a function that will handle a resource creation.
|
||||||
func CreateResource(r rest.Creater, ctxFn ContextFunc, namer ScopeNamer, codec runtime.Codec, typer runtime.ObjectTyper, resource string, admit admission.Interface) 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 := namer.Namespace(req)
|
namespace, err := scope.Namer.Namespace(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errorJSON(err, codec, w)
|
errorJSON(err, scope.Codec, w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
ctx := ctxFn(req)
|
ctx := scope.ContextFunc(req)
|
||||||
ctx = api.WithNamespace(ctx, namespace)
|
ctx = api.WithNamespace(ctx, namespace)
|
||||||
|
|
||||||
body, err := readBody(req.Request)
|
body, err := readBody(req.Request)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errorJSON(err, codec, w)
|
errorJSON(err, scope.Codec, w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
obj := r.New()
|
obj := r.New()
|
||||||
if err := codec.DecodeInto(body, obj); err != nil {
|
if err := scope.Codec.DecodeInto(body, obj); err != nil {
|
||||||
err = transformDecodeError(typer, err, obj, body)
|
err = transformDecodeError(typer, err, obj, body)
|
||||||
errorJSON(err, codec, w)
|
errorJSON(err, scope.Codec, w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err = admit.Admit(admission.NewAttributesRecord(obj, namespace, resource, "CREATE"))
|
err = admit.Admit(admission.NewAttributesRecord(obj, namespace, scope.Resource, "CREATE"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errorJSON(err, codec, w)
|
errorJSON(err, scope.Codec, w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -177,73 +187,73 @@ func CreateResource(r rest.Creater, ctxFn ContextFunc, namer ScopeNamer, codec r
|
|||||||
return out, err
|
return out, err
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errorJSON(err, codec, w)
|
errorJSON(err, scope.Codec, w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := setSelfLink(result, req, namer); err != nil {
|
if err := setSelfLink(result, req, scope.Namer); err != nil {
|
||||||
errorJSON(err, codec, w)
|
errorJSON(err, scope.Codec, w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
writeJSON(http.StatusCreated, codec, result, w)
|
write(http.StatusCreated, scope.APIVersion, scope.Codec, result, w, req.Request)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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 AtomicUpdate and this routine should be a bit cleaner
|
// TODO: Eventually PatchResource should just use AtomicUpdate and this routine should be a bit cleaner
|
||||||
func PatchResource(r rest.Patcher, ctxFn ContextFunc, namer ScopeNamer, codec runtime.Codec, typer runtime.ObjectTyper, resource string, admit admission.Interface) restful.RouteFunction {
|
func PatchResource(r rest.Patcher, 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, name, err := namer.Name(req)
|
namespace, name, err := scope.Namer.Name(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errorJSON(err, codec, w)
|
errorJSON(err, scope.Codec, w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
obj := r.New()
|
obj := r.New()
|
||||||
// PATCH requires same permission as UPDATE
|
// PATCH requires same permission as UPDATE
|
||||||
err = admit.Admit(admission.NewAttributesRecord(obj, namespace, resource, "UPDATE"))
|
err = admit.Admit(admission.NewAttributesRecord(obj, namespace, scope.Resource, "UPDATE"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errorJSON(err, codec, w)
|
errorJSON(err, scope.Codec, w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx := ctxFn(req)
|
ctx := scope.ContextFunc(req)
|
||||||
ctx = api.WithNamespace(ctx, namespace)
|
ctx = api.WithNamespace(ctx, namespace)
|
||||||
|
|
||||||
original, err := r.Get(ctx, name)
|
original, err := r.Get(ctx, name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errorJSON(err, codec, w)
|
errorJSON(err, scope.Codec, w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
originalObjJs, err := codec.Encode(original)
|
originalObjJs, err := scope.Codec.Encode(original)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errorJSON(err, codec, w)
|
errorJSON(err, scope.Codec, w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
patchJs, err := readBody(req.Request)
|
patchJs, err := readBody(req.Request)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errorJSON(err, codec, w)
|
errorJSON(err, scope.Codec, w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
patchedObjJs, err := jsonpatch.MergePatch(originalObjJs, patchJs)
|
patchedObjJs, err := jsonpatch.MergePatch(originalObjJs, patchJs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errorJSON(err, codec, w)
|
errorJSON(err, scope.Codec, w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := codec.DecodeInto(patchedObjJs, obj); err != nil {
|
if err := scope.Codec.DecodeInto(patchedObjJs, obj); err != nil {
|
||||||
errorJSON(err, codec, w)
|
errorJSON(err, scope.Codec, w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err := checkName(obj, name, namespace, namer); err != nil {
|
if err := checkName(obj, name, namespace, scope.Namer); err != nil {
|
||||||
errorJSON(err, codec, w)
|
errorJSON(err, scope.Codec, w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -253,56 +263,56 @@ func PatchResource(r rest.Patcher, ctxFn ContextFunc, namer ScopeNamer, codec ru
|
|||||||
return obj, err
|
return obj, err
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errorJSON(err, codec, w)
|
errorJSON(err, scope.Codec, w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := setSelfLink(result, req, namer); err != nil {
|
if err := setSelfLink(result, req, scope.Namer); err != nil {
|
||||||
errorJSON(err, codec, w)
|
errorJSON(err, scope.Codec, w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
writeJSON(http.StatusOK, codec, result, w)
|
write(http.StatusOK, scope.APIVersion, scope.Codec, result, w, req.Request)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateResource returns a function that will handle a resource update
|
// UpdateResource returns a function that will handle a resource update
|
||||||
func UpdateResource(r rest.Updater, ctxFn ContextFunc, namer ScopeNamer, codec runtime.Codec, typer runtime.ObjectTyper, resource string, admit admission.Interface) restful.RouteFunction {
|
func UpdateResource(r rest.Updater, 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, name, err := namer.Name(req)
|
namespace, name, err := scope.Namer.Name(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errorJSON(err, codec, w)
|
errorJSON(err, scope.Codec, w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
ctx := ctxFn(req)
|
ctx := scope.ContextFunc(req)
|
||||||
ctx = api.WithNamespace(ctx, namespace)
|
ctx = api.WithNamespace(ctx, namespace)
|
||||||
|
|
||||||
body, err := readBody(req.Request)
|
body, err := readBody(req.Request)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errorJSON(err, codec, w)
|
errorJSON(err, scope.Codec, w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
obj := r.New()
|
obj := r.New()
|
||||||
if err := codec.DecodeInto(body, obj); err != nil {
|
if err := scope.Codec.DecodeInto(body, obj); err != nil {
|
||||||
err = transformDecodeError(typer, err, obj, body)
|
err = transformDecodeError(typer, err, obj, body)
|
||||||
errorJSON(err, codec, w)
|
errorJSON(err, scope.Codec, w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := checkName(obj, name, namespace, namer); err != nil {
|
if err := checkName(obj, name, namespace, scope.Namer); err != nil {
|
||||||
errorJSON(err, codec, w)
|
errorJSON(err, scope.Codec, w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err = admit.Admit(admission.NewAttributesRecord(obj, namespace, resource, "UPDATE"))
|
err = admit.Admit(admission.NewAttributesRecord(obj, namespace, scope.Resource, "UPDATE"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errorJSON(err, codec, w)
|
errorJSON(err, scope.Codec, w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -313,12 +323,12 @@ func UpdateResource(r rest.Updater, ctxFn ContextFunc, namer ScopeNamer, codec r
|
|||||||
return obj, err
|
return obj, err
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errorJSON(err, codec, w)
|
errorJSON(err, scope.Codec, w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := setSelfLink(result, req, namer); err != nil {
|
if err := setSelfLink(result, req, scope.Namer); err != nil {
|
||||||
errorJSON(err, codec, w)
|
errorJSON(err, scope.Codec, w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -326,46 +336,44 @@ func UpdateResource(r rest.Updater, ctxFn ContextFunc, namer ScopeNamer, codec r
|
|||||||
if wasCreated {
|
if wasCreated {
|
||||||
status = http.StatusCreated
|
status = http.StatusCreated
|
||||||
}
|
}
|
||||||
writeJSON(status, codec, result, w)
|
writeJSON(status, scope.Codec, result, w)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteResource returns a function that will handle a resource deletion
|
// DeleteResource returns a function that will handle a resource deletion
|
||||||
func DeleteResource(r rest.GracefulDeleter, checkBody bool, ctxFn ContextFunc, namer ScopeNamer, codec runtime.Codec, resource, kind string, admit admission.Interface) restful.RouteFunction {
|
func DeleteResource(r rest.GracefulDeleter, checkBody bool, scope RequestScope, 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, name, err := namer.Name(req)
|
namespace, name, err := scope.Namer.Name(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errorJSON(err, codec, w)
|
errorJSON(err, scope.Codec, w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
ctx := ctxFn(req)
|
ctx := scope.ContextFunc(req)
|
||||||
if len(namespace) > 0 {
|
|
||||||
ctx = api.WithNamespace(ctx, namespace)
|
ctx = api.WithNamespace(ctx, namespace)
|
||||||
}
|
|
||||||
|
|
||||||
options := &api.DeleteOptions{}
|
options := &api.DeleteOptions{}
|
||||||
if checkBody {
|
if checkBody {
|
||||||
body, err := readBody(req.Request)
|
body, err := readBody(req.Request)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errorJSON(err, codec, w)
|
errorJSON(err, scope.Codec, w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if len(body) > 0 {
|
if len(body) > 0 {
|
||||||
if err := codec.DecodeInto(body, options); err != nil {
|
if err := scope.Codec.DecodeInto(body, options); err != nil {
|
||||||
errorJSON(err, codec, w)
|
errorJSON(err, scope.Codec, w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
err = admit.Admit(admission.NewAttributesRecord(nil, namespace, resource, "DELETE"))
|
err = admit.Admit(admission.NewAttributesRecord(nil, namespace, scope.Resource, "DELETE"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errorJSON(err, codec, w)
|
errorJSON(err, scope.Codec, w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -373,7 +381,7 @@ func DeleteResource(r rest.GracefulDeleter, checkBody bool, ctxFn ContextFunc, n
|
|||||||
return r.Delete(ctx, name, options)
|
return r.Delete(ctx, name, options)
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errorJSON(err, codec, w)
|
errorJSON(err, scope.Codec, w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -385,19 +393,19 @@ func DeleteResource(r rest.GracefulDeleter, checkBody bool, ctxFn ContextFunc, n
|
|||||||
Code: http.StatusOK,
|
Code: http.StatusOK,
|
||||||
Details: &api.StatusDetails{
|
Details: &api.StatusDetails{
|
||||||
ID: name,
|
ID: name,
|
||||||
Kind: kind,
|
Kind: scope.Kind,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// when a non-status response is returned, set the self link
|
// when a non-status response is returned, set the self link
|
||||||
if _, ok := result.(*api.Status); !ok {
|
if _, ok := result.(*api.Status); !ok {
|
||||||
if err := setSelfLink(result, req, namer); err != nil {
|
if err := setSelfLink(result, req, scope.Namer); err != nil {
|
||||||
errorJSON(err, codec, w)
|
errorJSON(err, scope.Codec, w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
writeJSON(http.StatusOK, codec, result, w)
|
write(http.StatusOK, scope.APIVersion, scope.Codec, result, w, req.Request)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -449,11 +457,8 @@ func transformDecodeError(typer runtime.ObjectTyper, baseErr error, into runtime
|
|||||||
func setSelfLink(obj runtime.Object, req *restful.Request, namer ScopeNamer) error {
|
func setSelfLink(obj runtime.Object, req *restful.Request, namer ScopeNamer) error {
|
||||||
// TODO: SelfLink generation should return a full URL?
|
// TODO: SelfLink generation should return a full URL?
|
||||||
path, query, err := namer.GenerateLink(req, obj)
|
path, query, err := namer.GenerateLink(req, obj)
|
||||||
if err == errEmptyName {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
newURL := *req.Request.URL
|
newURL := *req.Request.URL
|
||||||
|
Loading…
Reference in New Issue
Block a user