mirror of
				https://github.com/optim-enterprises-bv/kubernetes.git
				synced 2025-11-04 04:08:16 +00:00 
			
		
		
		
	Allow InputStreams to be returned by requests
Add additional metadata to restful services.
This commit is contained in:
		@@ -17,6 +17,7 @@ limitations under the License.
 | 
			
		||||
package rest
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"io"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"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(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)
 | 
			
		||||
	_, isWatcher := storage.(rest.Watcher)
 | 
			
		||||
	_, isRedirector := storage.(rest.Redirector)
 | 
			
		||||
	storageMeta, isMetadata := storage.(rest.StorageMetadata)
 | 
			
		||||
	if !isMetadata {
 | 
			
		||||
		storageMeta = defaultStorageMetadata{}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var versionedDeleterObject runtime.Object
 | 
			
		||||
	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
 | 
			
		||||
 | 
			
		||||
	reqScope := RequestScope{
 | 
			
		||||
		ContextFunc: ctxFn,
 | 
			
		||||
		Codec:       mapping.Codec,
 | 
			
		||||
		APIVersion:  a.group.Version,
 | 
			
		||||
		Resource:    resource,
 | 
			
		||||
		Kind:        kind,
 | 
			
		||||
	}
 | 
			
		||||
	for _, action := range actions {
 | 
			
		||||
		reqScope.Namer = action.Namer
 | 
			
		||||
		m := monitorFilter(action.Verb, resource)
 | 
			
		||||
		switch action.Verb {
 | 
			
		||||
		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).
 | 
			
		||||
				Doc("read the specified " + kind).
 | 
			
		||||
				Operation("read" + kind).
 | 
			
		||||
				Produces(append(storageMeta.ProducesMIMETypes(action.Verb), "application/json")...).
 | 
			
		||||
				Writes(versionedObject)
 | 
			
		||||
			addParams(route, action.Params)
 | 
			
		||||
			ws.Route(route)
 | 
			
		||||
		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).
 | 
			
		||||
				Doc("list objects of kind " + kind).
 | 
			
		||||
				Operation("list" + kind).
 | 
			
		||||
				Produces("application/json").
 | 
			
		||||
				Writes(versionedList)
 | 
			
		||||
			addParams(route, action.Params)
 | 
			
		||||
			ws.Route(route)
 | 
			
		||||
		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).
 | 
			
		||||
				Doc("replace the specified " + kind).
 | 
			
		||||
				Operation("replace" + kind).
 | 
			
		||||
				Produces(append(storageMeta.ProducesMIMETypes(action.Verb), "application/json")...).
 | 
			
		||||
				Reads(versionedObject)
 | 
			
		||||
			addParams(route, action.Params)
 | 
			
		||||
			ws.Route(route)
 | 
			
		||||
		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).
 | 
			
		||||
				Doc("partially update the specified " + kind).
 | 
			
		||||
				// TODO: toggle patch strategy by content type
 | 
			
		||||
				// Consumes("application/merge-patch+json", "application/json-patch+json").
 | 
			
		||||
				Operation("patch" + kind).
 | 
			
		||||
				Produces(append(storageMeta.ProducesMIMETypes(action.Verb), "application/json")...).
 | 
			
		||||
				Reads(versionedObject)
 | 
			
		||||
			addParams(route, action.Params)
 | 
			
		||||
			ws.Route(route)
 | 
			
		||||
		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).
 | 
			
		||||
				Doc("create a " + kind).
 | 
			
		||||
				Operation("create" + kind).
 | 
			
		||||
				Produces(append(storageMeta.ProducesMIMETypes(action.Verb), "application/json")...).
 | 
			
		||||
				Reads(versionedObject)
 | 
			
		||||
			addParams(route, action.Params)
 | 
			
		||||
			ws.Route(route)
 | 
			
		||||
		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).
 | 
			
		||||
				Doc("delete a " + kind).
 | 
			
		||||
				Operation("delete" + kind)
 | 
			
		||||
				Operation("delete" + kind).
 | 
			
		||||
				Produces(append(storageMeta.ProducesMIMETypes(action.Verb), "application/json")...)
 | 
			
		||||
			if isGracefulDeleter {
 | 
			
		||||
				route.Reads(versionedDeleterObject)
 | 
			
		||||
			}
 | 
			
		||||
@@ -343,6 +361,7 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
 | 
			
		||||
				Filter(m).
 | 
			
		||||
				Doc("watch a particular " + kind).
 | 
			
		||||
				Operation("watch" + kind).
 | 
			
		||||
				Produces("application/json").
 | 
			
		||||
				Writes(versionedObject)
 | 
			
		||||
			addParams(route, action.Params)
 | 
			
		||||
			ws.Route(route)
 | 
			
		||||
@@ -351,6 +370,7 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
 | 
			
		||||
				Filter(m).
 | 
			
		||||
				Doc("watch a list of " + kind).
 | 
			
		||||
				Operation("watch" + kind + "list").
 | 
			
		||||
				Produces("application/json").
 | 
			
		||||
				Writes(versionedList)
 | 
			
		||||
			addParams(route, action.Params)
 | 
			
		||||
			ws.Route(route)
 | 
			
		||||
@@ -630,3 +650,13 @@ func addParams(route *restful.RouteBuilder, params []*restful.Parameter) {
 | 
			
		||||
		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"
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io"
 | 
			
		||||
	"io/ioutil"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"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.
 | 
			
		||||
func writeJSON(statusCode int, codec runtime.Codec, object runtime.Object, w http.ResponseWriter) {
 | 
			
		||||
	output, err := codec.Encode(object)
 | 
			
		||||
 
 | 
			
		||||
@@ -21,6 +21,7 @@ import (
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"errors"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io"
 | 
			
		||||
	"io/ioutil"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"net/http/httptest"
 | 
			
		||||
@@ -122,7 +123,8 @@ func init() {
 | 
			
		||||
// defaultAPIServer exposes nested objects for testability.
 | 
			
		||||
type defaultAPIServer struct {
 | 
			
		||||
	http.Handler
 | 
			
		||||
	group *APIGroupVersion
 | 
			
		||||
	group     *APIGroupVersion
 | 
			
		||||
	container *restful.Container
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// uses the default settings
 | 
			
		||||
@@ -169,7 +171,7 @@ func handleInternal(storage map[string]rest.Storage, admissionControl admission.
 | 
			
		||||
	ws := new(restful.WebService)
 | 
			
		||||
	InstallSupport(mux, ws)
 | 
			
		||||
	container.Add(ws)
 | 
			
		||||
	return &defaultAPIServer{mux, group}
 | 
			
		||||
	return &defaultAPIServer{mux, group, container}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Simple struct {
 | 
			
		||||
@@ -212,6 +214,8 @@ type SimpleRESTStorage struct {
 | 
			
		||||
	updated *Simple
 | 
			
		||||
	created *Simple
 | 
			
		||||
 | 
			
		||||
	stream *SimpleStream
 | 
			
		||||
 | 
			
		||||
	deleted       string
 | 
			
		||||
	deleteOptions *api.DeleteOptions
 | 
			
		||||
 | 
			
		||||
@@ -243,8 +247,34 @@ func (storage *SimpleRESTStorage) List(ctx api.Context, label labels.Selector, f
 | 
			
		||||
	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) {
 | 
			
		||||
	storage.checkContext(ctx)
 | 
			
		||||
	if id == "binary" {
 | 
			
		||||
		return storage.stream, 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)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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) {
 | 
			
		||||
	defer response.Body.Close()
 | 
			
		||||
	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) {
 | 
			
		||||
	storage := map[string]rest.Storage{}
 | 
			
		||||
	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) {
 | 
			
		||||
	storage := map[string]rest.Storage{}
 | 
			
		||||
	simpleStorage := SimpleRESTStorage{
 | 
			
		||||
 
 | 
			
		||||
@@ -59,28 +59,38 @@ type ScopeNamer interface {
 | 
			
		||||
	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.
 | 
			
		||||
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) {
 | 
			
		||||
		w := res.ResponseWriter
 | 
			
		||||
		namespace, name, err := namer.Name(req)
 | 
			
		||||
		namespace, name, err := scope.Namer.Name(req)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			errorJSON(err, codec, w)
 | 
			
		||||
			errorJSON(err, scope.Codec, w)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		ctx := ctxFn(req)
 | 
			
		||||
		ctx := scope.ContextFunc(req)
 | 
			
		||||
		ctx = api.WithNamespace(ctx, namespace)
 | 
			
		||||
 | 
			
		||||
		result, err := r.Get(ctx, name)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			errorJSON(err, codec, w)
 | 
			
		||||
			errorJSON(err, scope.Codec, w)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		if err := setSelfLink(result, req, namer); err != nil {
 | 
			
		||||
			errorJSON(err, codec, w)
 | 
			
		||||
		if err := setSelfLink(result, req, scope.Namer); err != nil {
 | 
			
		||||
			errorJSON(err, scope.Codec, w)
 | 
			
		||||
			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.
 | 
			
		||||
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) {
 | 
			
		||||
		w := res.ResponseWriter
 | 
			
		||||
 | 
			
		||||
		namespace, err := namer.Namespace(req)
 | 
			
		||||
		namespace, err := scope.Namer.Namespace(req)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			errorJSON(err, codec, w)
 | 
			
		||||
			errorJSON(err, scope.Codec, w)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		ctx := ctxFn(req)
 | 
			
		||||
		ctx := scope.ContextFunc(req)
 | 
			
		||||
		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 {
 | 
			
		||||
			errorJSON(err, codec, w)
 | 
			
		||||
			errorJSON(err, scope.Codec, w)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		result, err := r.List(ctx, label, field)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			errorJSON(err, codec, w)
 | 
			
		||||
			errorJSON(err, scope.Codec, w)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		if err := setListSelfLink(result, req, namer); err != nil {
 | 
			
		||||
			errorJSON(err, codec, w)
 | 
			
		||||
		if err := setListSelfLink(result, req, scope.Namer); err != nil {
 | 
			
		||||
			errorJSON(err, scope.Codec, w)
 | 
			
		||||
			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.
 | 
			
		||||
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) {
 | 
			
		||||
		w := res.ResponseWriter
 | 
			
		||||
 | 
			
		||||
		// TODO: we either want to remove timeout or document it (if we document, move timeout out of this function and declare it in api_installer)
 | 
			
		||||
		timeout := parseTimeout(req.Request.URL.Query().Get("timeout"))
 | 
			
		||||
 | 
			
		||||
		namespace, err := namer.Namespace(req)
 | 
			
		||||
		namespace, err := scope.Namer.Namespace(req)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			errorJSON(err, codec, w)
 | 
			
		||||
			errorJSON(err, scope.Codec, w)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		ctx := ctxFn(req)
 | 
			
		||||
		ctx := scope.ContextFunc(req)
 | 
			
		||||
		ctx = api.WithNamespace(ctx, namespace)
 | 
			
		||||
 | 
			
		||||
		body, err := readBody(req.Request)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			errorJSON(err, codec, w)
 | 
			
		||||
			errorJSON(err, scope.Codec, w)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		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)
 | 
			
		||||
			errorJSON(err, codec, w)
 | 
			
		||||
			errorJSON(err, scope.Codec, w)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		err = admit.Admit(admission.NewAttributesRecord(obj, namespace, resource, "CREATE"))
 | 
			
		||||
		err = admit.Admit(admission.NewAttributesRecord(obj, namespace, scope.Resource, "CREATE"))
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			errorJSON(err, codec, w)
 | 
			
		||||
			errorJSON(err, scope.Codec, w)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
@@ -177,73 +187,73 @@ func CreateResource(r rest.Creater, ctxFn ContextFunc, namer ScopeNamer, codec r
 | 
			
		||||
			return out, err
 | 
			
		||||
		})
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			errorJSON(err, codec, w)
 | 
			
		||||
			errorJSON(err, scope.Codec, w)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if err := setSelfLink(result, req, namer); err != nil {
 | 
			
		||||
			errorJSON(err, codec, w)
 | 
			
		||||
		if err := setSelfLink(result, req, scope.Namer); err != nil {
 | 
			
		||||
			errorJSON(err, scope.Codec, w)
 | 
			
		||||
			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
 | 
			
		||||
// 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) {
 | 
			
		||||
		w := res.ResponseWriter
 | 
			
		||||
 | 
			
		||||
		// TODO: we either want to remove timeout or document it (if we document, move timeout out of this function and declare it in api_installer)
 | 
			
		||||
		timeout := parseTimeout(req.Request.URL.Query().Get("timeout"))
 | 
			
		||||
 | 
			
		||||
		namespace, name, err := namer.Name(req)
 | 
			
		||||
		namespace, name, err := scope.Namer.Name(req)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			errorJSON(err, codec, w)
 | 
			
		||||
			errorJSON(err, scope.Codec, w)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		obj := r.New()
 | 
			
		||||
		// 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 {
 | 
			
		||||
			errorJSON(err, codec, w)
 | 
			
		||||
			errorJSON(err, scope.Codec, w)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		ctx := ctxFn(req)
 | 
			
		||||
		ctx := scope.ContextFunc(req)
 | 
			
		||||
		ctx = api.WithNamespace(ctx, namespace)
 | 
			
		||||
 | 
			
		||||
		original, err := r.Get(ctx, name)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			errorJSON(err, codec, w)
 | 
			
		||||
			errorJSON(err, scope.Codec, w)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		originalObjJs, err := codec.Encode(original)
 | 
			
		||||
		originalObjJs, err := scope.Codec.Encode(original)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			errorJSON(err, codec, w)
 | 
			
		||||
			errorJSON(err, scope.Codec, w)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		patchJs, err := readBody(req.Request)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			errorJSON(err, codec, w)
 | 
			
		||||
			errorJSON(err, scope.Codec, w)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		patchedObjJs, err := jsonpatch.MergePatch(originalObjJs, patchJs)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			errorJSON(err, codec, w)
 | 
			
		||||
			errorJSON(err, scope.Codec, w)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if err := codec.DecodeInto(patchedObjJs, obj); err != nil {
 | 
			
		||||
			errorJSON(err, codec, w)
 | 
			
		||||
		if err := scope.Codec.DecodeInto(patchedObjJs, obj); err != nil {
 | 
			
		||||
			errorJSON(err, scope.Codec, w)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		if err := checkName(obj, name, namespace, namer); err != nil {
 | 
			
		||||
			errorJSON(err, codec, w)
 | 
			
		||||
		if err := checkName(obj, name, namespace, scope.Namer); err != nil {
 | 
			
		||||
			errorJSON(err, scope.Codec, w)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
@@ -253,56 +263,56 @@ func PatchResource(r rest.Patcher, ctxFn ContextFunc, namer ScopeNamer, codec ru
 | 
			
		||||
			return obj, err
 | 
			
		||||
		})
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			errorJSON(err, codec, w)
 | 
			
		||||
			errorJSON(err, scope.Codec, w)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if err := setSelfLink(result, req, namer); err != nil {
 | 
			
		||||
			errorJSON(err, codec, w)
 | 
			
		||||
		if err := setSelfLink(result, req, scope.Namer); err != nil {
 | 
			
		||||
			errorJSON(err, scope.Codec, w)
 | 
			
		||||
			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
 | 
			
		||||
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) {
 | 
			
		||||
		w := res.ResponseWriter
 | 
			
		||||
 | 
			
		||||
		// TODO: we either want to remove timeout or document it (if we document, move timeout out of this function and declare it in api_installer)
 | 
			
		||||
		timeout := parseTimeout(req.Request.URL.Query().Get("timeout"))
 | 
			
		||||
 | 
			
		||||
		namespace, name, err := namer.Name(req)
 | 
			
		||||
		namespace, name, err := scope.Namer.Name(req)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			errorJSON(err, codec, w)
 | 
			
		||||
			errorJSON(err, scope.Codec, w)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		ctx := ctxFn(req)
 | 
			
		||||
		ctx := scope.ContextFunc(req)
 | 
			
		||||
		ctx = api.WithNamespace(ctx, namespace)
 | 
			
		||||
 | 
			
		||||
		body, err := readBody(req.Request)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			errorJSON(err, codec, w)
 | 
			
		||||
			errorJSON(err, scope.Codec, w)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		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)
 | 
			
		||||
			errorJSON(err, codec, w)
 | 
			
		||||
			errorJSON(err, scope.Codec, w)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if err := checkName(obj, name, namespace, namer); err != nil {
 | 
			
		||||
			errorJSON(err, codec, w)
 | 
			
		||||
		if err := checkName(obj, name, namespace, scope.Namer); err != nil {
 | 
			
		||||
			errorJSON(err, scope.Codec, w)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		err = admit.Admit(admission.NewAttributesRecord(obj, namespace, resource, "UPDATE"))
 | 
			
		||||
		err = admit.Admit(admission.NewAttributesRecord(obj, namespace, scope.Resource, "UPDATE"))
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			errorJSON(err, codec, w)
 | 
			
		||||
			errorJSON(err, scope.Codec, w)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
@@ -313,12 +323,12 @@ func UpdateResource(r rest.Updater, ctxFn ContextFunc, namer ScopeNamer, codec r
 | 
			
		||||
			return obj, err
 | 
			
		||||
		})
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			errorJSON(err, codec, w)
 | 
			
		||||
			errorJSON(err, scope.Codec, w)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if err := setSelfLink(result, req, namer); err != nil {
 | 
			
		||||
			errorJSON(err, codec, w)
 | 
			
		||||
		if err := setSelfLink(result, req, scope.Namer); err != nil {
 | 
			
		||||
			errorJSON(err, scope.Codec, w)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
@@ -326,46 +336,44 @@ func UpdateResource(r rest.Updater, ctxFn ContextFunc, namer ScopeNamer, codec r
 | 
			
		||||
		if wasCreated {
 | 
			
		||||
			status = http.StatusCreated
 | 
			
		||||
		}
 | 
			
		||||
		writeJSON(status, codec, result, w)
 | 
			
		||||
		writeJSON(status, scope.Codec, result, w)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 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) {
 | 
			
		||||
		w := res.ResponseWriter
 | 
			
		||||
 | 
			
		||||
		// TODO: we either want to remove timeout or document it (if we document, move timeout out of this function and declare it in api_installer)
 | 
			
		||||
		timeout := parseTimeout(req.Request.URL.Query().Get("timeout"))
 | 
			
		||||
 | 
			
		||||
		namespace, name, err := namer.Name(req)
 | 
			
		||||
		namespace, name, err := scope.Namer.Name(req)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			errorJSON(err, codec, w)
 | 
			
		||||
			errorJSON(err, scope.Codec, w)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		ctx := ctxFn(req)
 | 
			
		||||
		if len(namespace) > 0 {
 | 
			
		||||
			ctx = api.WithNamespace(ctx, namespace)
 | 
			
		||||
		}
 | 
			
		||||
		ctx := scope.ContextFunc(req)
 | 
			
		||||
		ctx = api.WithNamespace(ctx, namespace)
 | 
			
		||||
 | 
			
		||||
		options := &api.DeleteOptions{}
 | 
			
		||||
		if checkBody {
 | 
			
		||||
			body, err := readBody(req.Request)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				errorJSON(err, codec, w)
 | 
			
		||||
				errorJSON(err, scope.Codec, w)
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
			if len(body) > 0 {
 | 
			
		||||
				if err := codec.DecodeInto(body, options); err != nil {
 | 
			
		||||
					errorJSON(err, codec, w)
 | 
			
		||||
				if err := scope.Codec.DecodeInto(body, options); err != nil {
 | 
			
		||||
					errorJSON(err, scope.Codec, w)
 | 
			
		||||
					return
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		err = admit.Admit(admission.NewAttributesRecord(nil, namespace, resource, "DELETE"))
 | 
			
		||||
		err = admit.Admit(admission.NewAttributesRecord(nil, namespace, scope.Resource, "DELETE"))
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			errorJSON(err, codec, w)
 | 
			
		||||
			errorJSON(err, scope.Codec, w)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
@@ -373,7 +381,7 @@ func DeleteResource(r rest.GracefulDeleter, checkBody bool, ctxFn ContextFunc, n
 | 
			
		||||
			return r.Delete(ctx, name, options)
 | 
			
		||||
		})
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			errorJSON(err, codec, w)
 | 
			
		||||
			errorJSON(err, scope.Codec, w)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
@@ -385,19 +393,19 @@ func DeleteResource(r rest.GracefulDeleter, checkBody bool, ctxFn ContextFunc, n
 | 
			
		||||
				Code:   http.StatusOK,
 | 
			
		||||
				Details: &api.StatusDetails{
 | 
			
		||||
					ID:   name,
 | 
			
		||||
					Kind: kind,
 | 
			
		||||
					Kind: scope.Kind,
 | 
			
		||||
				},
 | 
			
		||||
			}
 | 
			
		||||
		} else {
 | 
			
		||||
			// when a non-status response is returned, set the self link
 | 
			
		||||
			if _, ok := result.(*api.Status); !ok {
 | 
			
		||||
				if err := setSelfLink(result, req, namer); err != nil {
 | 
			
		||||
					errorJSON(err, codec, w)
 | 
			
		||||
				if err := setSelfLink(result, req, scope.Namer); err != nil {
 | 
			
		||||
					errorJSON(err, scope.Codec, w)
 | 
			
		||||
					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 {
 | 
			
		||||
	// TODO: SelfLink generation should return a full URL?
 | 
			
		||||
	path, query, err := namer.GenerateLink(req, obj)
 | 
			
		||||
	if err == errEmptyName {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	newURL := *req.Request.URL
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user