mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-30 06:54:01 +00:00
Merge pull request #2813 from derekwaynecarr/ns_url
Move namespace from query param to path part
This commit is contained in:
commit
3ade280f89
@ -100,7 +100,7 @@ func indirectArbitraryPointer(ptrToObject interface{}) interface{} {
|
||||
return reflect.Indirect(reflect.ValueOf(ptrToObject)).Interface()
|
||||
}
|
||||
|
||||
func registerResourceHandlers(ws *restful.WebService, version string, path string, storage RESTStorage, kinds map[string]reflect.Type, h restful.RouteFunction) {
|
||||
func registerResourceHandlers(ws *restful.WebService, version string, path string, storage RESTStorage, kinds map[string]reflect.Type, h restful.RouteFunction, namespaceScope bool) {
|
||||
glog.V(3).Infof("Installing /%s/%s\n", version, path)
|
||||
object := storage.New()
|
||||
_, kind, err := api.Scheme.ObjectVersionAndKind(object)
|
||||
@ -118,6 +118,11 @@ func registerResourceHandlers(ws *restful.WebService, version string, path strin
|
||||
|
||||
// See github.com/emicklei/go-restful/blob/master/jsr311.go for routing logic
|
||||
// and status-code behavior
|
||||
if namespaceScope {
|
||||
path = "ns/{namespace}/" + path
|
||||
ws.Param(ws.PathParameter("namespace", "object name and auth scope, such as for teams and projects").DataType("string"))
|
||||
}
|
||||
glog.V(3).Infof("Installing version=/%s, kind=/%s, path=/%s\n", version, kind, path)
|
||||
|
||||
ws.Route(ws.POST(path).To(h).
|
||||
Doc("create a " + kind).
|
||||
@ -221,7 +226,10 @@ func (g *APIGroupVersion) InstallREST(container *restful.Container, root string,
|
||||
}
|
||||
|
||||
for path, storage := range g.handler.storage {
|
||||
registerResourceHandlers(ws, version, path, storage, kinds, h)
|
||||
// register legacy patterns where namespace is optional in path
|
||||
registerResourceHandlers(ws, version, path, storage, kinds, h, false)
|
||||
// register pattern where namespace is required in path
|
||||
registerResourceHandlers(ws, version, path, storage, kinds, h, true)
|
||||
}
|
||||
|
||||
// TODO: port the rest of these. Sadly, if we don't, we'll have inconsistent
|
||||
|
@ -683,7 +683,7 @@ func TestSyncCreate(t *testing.T) {
|
||||
t: t,
|
||||
name: "bar",
|
||||
namespace: "other",
|
||||
expectedSet: "/prefix/version/foo/bar?namespace=other",
|
||||
expectedSet: "/prefix/version/ns/other/foo/bar",
|
||||
}
|
||||
handler := Handle(map[string]RESTStorage{
|
||||
"foo": &storage,
|
||||
@ -696,7 +696,7 @@ func TestSyncCreate(t *testing.T) {
|
||||
Other: "bar",
|
||||
}
|
||||
data, _ := codec.Encode(simple)
|
||||
request, err := http.NewRequest("POST", server.URL+"/prefix/version/foo?sync=true", bytes.NewBuffer(data))
|
||||
request, err := http.NewRequest("POST", server.URL+"/prefix/version/ns/other/foo?sync=true", bytes.NewBuffer(data))
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
|
@ -23,6 +23,7 @@ import (
|
||||
"runtime/debug"
|
||||
"strings"
|
||||
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/auth/authorizer"
|
||||
authhandlers "github.com/GoogleCloudPlatform/kubernetes/pkg/auth/handlers"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/httplog"
|
||||
@ -40,24 +41,6 @@ var specialVerbs = map[string]bool{
|
||||
"watch": true,
|
||||
}
|
||||
|
||||
// KindFromRequest returns Kind if Kind can be extracted from the request. Otherwise, the empty string.
|
||||
func KindFromRequest(req http.Request) string {
|
||||
// TODO: find a way to keep this code's assumptions about paths up to date with changes in the code. Maybe instead
|
||||
// of directly adding handler's code to the master's Mux, have a function which forces the structure when adding
|
||||
// them.
|
||||
parts := splitPath(req.URL.Path)
|
||||
if len(parts) > 2 && parts[0] == "api" {
|
||||
if _, ok := specialVerbs[parts[2]]; ok {
|
||||
if len(parts) > 3 {
|
||||
return parts[3]
|
||||
}
|
||||
} else {
|
||||
return parts[2]
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// IsReadOnlyReq() is true for any (or at least many) request which has no observable
|
||||
// side effects on state of apiserver (though there may be internal side effects like
|
||||
// caching and logging).
|
||||
@ -185,14 +168,16 @@ func (r *requestAttributeGetter) GetAttribs(req *http.Request) authorizer.Attrib
|
||||
|
||||
attribs.ReadOnly = IsReadOnlyReq(*req)
|
||||
|
||||
namespace, kind, _, _ := KindAndNamespace(req)
|
||||
|
||||
// If a path follows the conventions of the REST object store, then
|
||||
// we can extract the object Kind. Otherwise, not.
|
||||
attribs.Kind = KindFromRequest(*req)
|
||||
attribs.Kind = kind
|
||||
|
||||
// If the request specifies a namespace, then the namespace is filled in.
|
||||
// Assumes there is no empty string namespace. Unspecified results
|
||||
// in empty (does not understand defaulting rules.)
|
||||
attribs.Namespace = req.URL.Query().Get("namespace")
|
||||
attribs.Namespace = namespace
|
||||
|
||||
return &attribs
|
||||
}
|
||||
@ -208,3 +193,79 @@ func WithAuthorizationCheck(handler http.Handler, getAttribs RequestAttributeGet
|
||||
forbidden(w, req)
|
||||
})
|
||||
}
|
||||
|
||||
// KindAndNamespace returns the kind, namespace, and path parts for the request relative to /{kind}/{name}
|
||||
// Valid Inputs:
|
||||
// Storage paths
|
||||
// /ns/{namespace}/{kind}
|
||||
// /ns/{namespace}/{kind}/{resourceName}
|
||||
// /{kind}
|
||||
// /{kind}/{resourceName}
|
||||
// /{kind}/{resourceName}?namespace={namespace}
|
||||
// /{kind}?namespace={namespace}
|
||||
//
|
||||
// Special verbs:
|
||||
// /proxy/{kind}/{resourceName}
|
||||
// /proxy/ns/{namespace}/{kind}/{resourceName}
|
||||
// /redirect/ns/{namespace}/{kind}/{resourceName}
|
||||
// /redirect/{kind}/{resourceName}
|
||||
// /watch/{kind}
|
||||
// /watch/ns/{namespace}/{kind}
|
||||
//
|
||||
// Fully qualified paths for above:
|
||||
// /api/{version}/*
|
||||
// /api/{version}/*
|
||||
func KindAndNamespace(req *http.Request) (namespace, kind string, parts []string, err error) {
|
||||
parts = splitPath(req.URL.Path)
|
||||
if len(parts) < 1 {
|
||||
err = fmt.Errorf("Unable to determine kind and namespace from an empty URL path")
|
||||
return
|
||||
}
|
||||
|
||||
// handle input of form /api/{version}/* by adjusting special paths
|
||||
if parts[0] == "api" {
|
||||
if len(parts) > 2 {
|
||||
parts = parts[2:]
|
||||
} else {
|
||||
err = fmt.Errorf("Unable to determine kind and namespace from url, %v", req.URL)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// handle input of form /{specialVerb}/*
|
||||
if _, ok := specialVerbs[parts[0]]; ok {
|
||||
if len(parts) > 1 {
|
||||
parts = parts[1:]
|
||||
} else {
|
||||
err = fmt.Errorf("Unable to determine kind and namespace from url, %v", req.URL)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// URL forms: /ns/{namespace}/{kind}/*, where parts are adjusted to be relative to kind
|
||||
if parts[0] == "ns" {
|
||||
if len(parts) < 3 {
|
||||
err = fmt.Errorf("ResourceTypeAndNamespace expects a path of form /ns/{namespace}/*")
|
||||
return
|
||||
}
|
||||
namespace = parts[1]
|
||||
kind = parts[2]
|
||||
parts = parts[2:]
|
||||
return
|
||||
}
|
||||
|
||||
// URL forms: /{kind}/*
|
||||
// URL forms: POST /{kind} is a legacy API convention to create in "default" namespace
|
||||
// URL forms: /{kind}/{resourceName} use the "default" namespace if omitted from query param
|
||||
// URL forms: /{kind} assume cross-namespace operation if omitted from query param
|
||||
kind = parts[0]
|
||||
namespace = req.URL.Query().Get("namespace")
|
||||
if len(namespace) == 0 {
|
||||
if len(parts) > 1 || req.Method == "POST" {
|
||||
namespace = api.NamespaceDefault
|
||||
} else {
|
||||
namespace = api.NamespaceAll
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
@ -19,7 +19,10 @@ package apiserver
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||
)
|
||||
|
||||
type fakeRL bool
|
||||
@ -59,3 +62,78 @@ func TestReadOnly(t *testing.T) {
|
||||
http.DefaultClient.Do(req)
|
||||
}
|
||||
}
|
||||
|
||||
func TestKindAndNamespace(t *testing.T) {
|
||||
successCases := []struct {
|
||||
method string
|
||||
url string
|
||||
expectedNamespace string
|
||||
expectedKind string
|
||||
expectedParts []string
|
||||
}{
|
||||
// resource paths
|
||||
{"GET", "/ns/other/pods", "other", "pods", []string{"pods"}},
|
||||
{"GET", "/ns/other/pods/foo", "other", "pods", []string{"pods", "foo"}},
|
||||
{"GET", "/pods", api.NamespaceAll, "pods", []string{"pods"}},
|
||||
{"POST", "/pods", api.NamespaceDefault, "pods", []string{"pods"}},
|
||||
{"GET", "/pods/foo", api.NamespaceDefault, "pods", []string{"pods", "foo"}},
|
||||
{"GET", "/pods/foo?namespace=other", "other", "pods", []string{"pods", "foo"}},
|
||||
{"GET", "/pods?namespace=other", "other", "pods", []string{"pods"}},
|
||||
|
||||
// special verbs
|
||||
{"GET", "/proxy/ns/other/pods/foo", "other", "pods", []string{"pods", "foo"}},
|
||||
{"GET", "/proxy/pods/foo", api.NamespaceDefault, "pods", []string{"pods", "foo"}},
|
||||
{"GET", "/redirect/ns/other/pods/foo", "other", "pods", []string{"pods", "foo"}},
|
||||
{"GET", "/redirect/pods/foo", api.NamespaceDefault, "pods", []string{"pods", "foo"}},
|
||||
{"GET", "/watch/pods", api.NamespaceAll, "pods", []string{"pods"}},
|
||||
{"GET", "/watch/ns/other/pods", "other", "pods", []string{"pods"}},
|
||||
|
||||
// fully-qualified paths
|
||||
{"GET", "/api/v1beta1/ns/other/pods", "other", "pods", []string{"pods"}},
|
||||
{"GET", "/api/v1beta1/ns/other/pods/foo", "other", "pods", []string{"pods", "foo"}},
|
||||
{"GET", "/api/v1beta1/pods", api.NamespaceAll, "pods", []string{"pods"}},
|
||||
{"POST", "/api/v1beta1/pods", api.NamespaceDefault, "pods", []string{"pods"}},
|
||||
{"GET", "/api/v1beta1/pods/foo", api.NamespaceDefault, "pods", []string{"pods", "foo"}},
|
||||
{"GET", "/api/v1beta1/pods/foo?namespace=other", "other", "pods", []string{"pods", "foo"}},
|
||||
{"GET", "/api/v1beta1/pods?namespace=other", "other", "pods", []string{"pods"}},
|
||||
{"GET", "/api/v1beta1/proxy/pods/foo", api.NamespaceDefault, "pods", []string{"pods", "foo"}},
|
||||
{"GET", "/api/v1beta1/redirect/pods/foo", api.NamespaceDefault, "pods", []string{"pods", "foo"}},
|
||||
{"GET", "/api/v1beta1/watch/pods", api.NamespaceAll, "pods", []string{"pods"}},
|
||||
{"GET", "/api/v1beta1/watch/ns/other/pods", "other", "pods", []string{"pods"}},
|
||||
}
|
||||
|
||||
for _, successCase := range successCases {
|
||||
req, _ := http.NewRequest(successCase.method, successCase.url, nil)
|
||||
namespace, kind, parts, err := KindAndNamespace(req)
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error for url: %s", successCase.url)
|
||||
}
|
||||
if successCase.expectedNamespace != namespace {
|
||||
t.Errorf("Unexpected namespace for url: %s, expected: %s, actual: %s", successCase.url, successCase.expectedNamespace, namespace)
|
||||
}
|
||||
if successCase.expectedKind != kind {
|
||||
t.Errorf("Unexpected resourceType for url: %s, expected: %s, actual: %s", successCase.url, successCase.expectedKind, kind)
|
||||
}
|
||||
if !reflect.DeepEqual(successCase.expectedParts, parts) {
|
||||
t.Errorf("Unexpected parts for url: %s, expected: %v, actual: %v", successCase.url, successCase.expectedParts, parts)
|
||||
}
|
||||
}
|
||||
|
||||
errorCases := map[string]string{
|
||||
"no resource path": "/",
|
||||
"missing resource type": "/ns/other",
|
||||
"just apiversion": "/api/v1beta1/",
|
||||
"apiversion with no resource": "/api/v1beta1/",
|
||||
"apiversion with just namespace": "/api/v1beta1/ns/other",
|
||||
}
|
||||
for k, v := range errorCases {
|
||||
req, err := http.NewRequest("GET", v, nil)
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error %v", err)
|
||||
}
|
||||
_, _, _, err = KindAndNamespace(req)
|
||||
if err == nil {
|
||||
t.Errorf("Expected error for key: %s", k)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -78,35 +78,32 @@ type ProxyHandler struct {
|
||||
}
|
||||
|
||||
func (r *ProxyHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
// use the default namespace to address the service
|
||||
ctx := api.NewDefaultContext()
|
||||
// if not in default namespace, provide the query parameter
|
||||
// TODO this will need to go in the path in the future and not as a query parameter
|
||||
namespace := req.URL.Query().Get("namespace")
|
||||
if len(namespace) > 0 {
|
||||
ctx = api.WithNamespace(ctx, namespace)
|
||||
namespace, kind, parts, err := KindAndNamespace(req)
|
||||
if err != nil {
|
||||
notFound(w, req)
|
||||
return
|
||||
}
|
||||
parts := strings.SplitN(req.URL.Path, "/", 3)
|
||||
ctx := api.WithNamespace(api.NewContext(), namespace)
|
||||
if len(parts) < 2 {
|
||||
notFound(w, req)
|
||||
return
|
||||
}
|
||||
resourceName := parts[0]
|
||||
id := parts[1]
|
||||
rest := ""
|
||||
if len(parts) == 3 {
|
||||
rest = parts[2]
|
||||
if len(parts) > 2 {
|
||||
proxyParts := parts[2:]
|
||||
rest = strings.Join(proxyParts, "/")
|
||||
}
|
||||
storage, ok := r.storage[resourceName]
|
||||
storage, ok := r.storage[kind]
|
||||
if !ok {
|
||||
httplog.LogOf(req, w).Addf("'%v' has no storage object", resourceName)
|
||||
httplog.LogOf(req, w).Addf("'%v' has no storage object", kind)
|
||||
notFound(w, req)
|
||||
return
|
||||
}
|
||||
|
||||
redirector, ok := storage.(Redirector)
|
||||
if !ok {
|
||||
httplog.LogOf(req, w).Addf("'%v' is not a redirector", resourceName)
|
||||
httplog.LogOf(req, w).Addf("'%v' is not a redirector", kind)
|
||||
notFound(w, req)
|
||||
return
|
||||
}
|
||||
@ -150,7 +147,7 @@ func (r *ProxyHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
proxy.Transport = &proxyTransport{
|
||||
proxyScheme: req.URL.Scheme,
|
||||
proxyHost: req.URL.Host,
|
||||
proxyPathPrepend: path.Join(r.prefix, resourceName, id),
|
||||
proxyPathPrepend: path.Join(r.prefix, "ns", namespace, kind, id),
|
||||
}
|
||||
proxy.FlushInterval = 200 * time.Millisecond
|
||||
proxy.ServeHTTP(w, newReq)
|
||||
|
@ -142,7 +142,7 @@ func TestProxy(t *testing.T) {
|
||||
{"POST", "/some/other/dir", "question", "answer", "default"},
|
||||
{"PUT", "/some/dir/id", "different question", "answer", "default"},
|
||||
{"DELETE", "/some/dir/id", "", "ok", "default"},
|
||||
{"GET", "/some/dir/id?namespace=other", "", "answer", "other"},
|
||||
{"GET", "/some/dir/id", "", "answer", "other"},
|
||||
}
|
||||
|
||||
for _, item := range table {
|
||||
@ -154,6 +154,9 @@ func TestProxy(t *testing.T) {
|
||||
if e, a := item.reqBody, string(gotBody); e != a {
|
||||
t.Errorf("%v - expected %v, got %v", item.method, e, a)
|
||||
}
|
||||
if e, a := item.path, req.URL.Path; e != a {
|
||||
t.Errorf("%v - expected %v, got %v", item.method, e, a)
|
||||
}
|
||||
fmt.Fprint(w, item.respBody)
|
||||
}))
|
||||
defer proxyServer.Close()
|
||||
@ -169,27 +172,34 @@ func TestProxy(t *testing.T) {
|
||||
server := httptest.NewServer(handler)
|
||||
defer server.Close()
|
||||
|
||||
req, err := http.NewRequest(
|
||||
item.method,
|
||||
server.URL+"/prefix/version/proxy/foo/id"+item.path,
|
||||
strings.NewReader(item.reqBody),
|
||||
)
|
||||
if err != nil {
|
||||
t.Errorf("%v - unexpected error %v", item.method, err)
|
||||
continue
|
||||
// test each supported URL pattern for finding the redirection resource in the proxy in a particular namespace
|
||||
proxyTestPatterns := []string{
|
||||
"/prefix/version/proxy/foo/id" + item.path + "?namespace=" + item.reqNamespace,
|
||||
"/prefix/version/proxy/ns/" + item.reqNamespace + "/foo/id" + item.path,
|
||||
}
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
t.Errorf("%v - unexpected error %v", item.method, err)
|
||||
continue
|
||||
}
|
||||
gotResp, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
t.Errorf("%v - unexpected error %v", item.method, err)
|
||||
}
|
||||
resp.Body.Close()
|
||||
if e, a := item.respBody, string(gotResp); e != a {
|
||||
t.Errorf("%v - expected %v, got %v", item.method, e, a)
|
||||
for _, proxyTestPattern := range proxyTestPatterns {
|
||||
req, err := http.NewRequest(
|
||||
item.method,
|
||||
server.URL+proxyTestPattern,
|
||||
strings.NewReader(item.reqBody),
|
||||
)
|
||||
if err != nil {
|
||||
t.Errorf("%v - unexpected error %v", item.method, err)
|
||||
continue
|
||||
}
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
t.Errorf("%v - unexpected error %v", item.method, err)
|
||||
continue
|
||||
}
|
||||
gotResp, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
t.Errorf("%v - unexpected error %v", item.method, err)
|
||||
}
|
||||
resp.Body.Close()
|
||||
if e, a := item.respBody, string(gotResp); e != a {
|
||||
t.Errorf("%v - expected %v, got %v", item.method, e, a)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -30,28 +30,29 @@ type RedirectHandler struct {
|
||||
}
|
||||
|
||||
func (r *RedirectHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
ctx := api.NewDefaultContext()
|
||||
namespace := req.URL.Query().Get("namespace")
|
||||
if len(namespace) > 0 {
|
||||
ctx = api.WithNamespace(ctx, namespace)
|
||||
namespace, kind, parts, err := KindAndNamespace(req)
|
||||
if err != nil {
|
||||
notFound(w, req)
|
||||
return
|
||||
}
|
||||
parts := splitPath(req.URL.Path)
|
||||
ctx := api.WithNamespace(api.NewContext(), namespace)
|
||||
|
||||
// redirection requires /kind/resourceName path parts
|
||||
if len(parts) != 2 || req.Method != "GET" {
|
||||
notFound(w, req)
|
||||
return
|
||||
}
|
||||
resourceName := parts[0]
|
||||
id := parts[1]
|
||||
storage, ok := r.storage[resourceName]
|
||||
storage, ok := r.storage[kind]
|
||||
if !ok {
|
||||
httplog.LogOf(req, w).Addf("'%v' has no storage object", resourceName)
|
||||
httplog.LogOf(req, w).Addf("'%v' has no storage object", kind)
|
||||
notFound(w, req)
|
||||
return
|
||||
}
|
||||
|
||||
redirector, ok := storage.(Redirector)
|
||||
if !ok {
|
||||
httplog.LogOf(req, w).Addf("'%v' is not a redirector", resourceName)
|
||||
httplog.LogOf(req, w).Addf("'%v' is not a redirector", kind)
|
||||
notFound(w, req)
|
||||
return
|
||||
}
|
||||
|
@ -76,3 +76,56 @@ func TestRedirect(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestRedirectWithNamespaces(t *testing.T) {
|
||||
simpleStorage := &SimpleRESTStorage{
|
||||
errors: map[string]error{},
|
||||
expectedResourceNamespace: "other",
|
||||
}
|
||||
handler := Handle(map[string]RESTStorage{
|
||||
"foo": simpleStorage,
|
||||
}, codec, "/prefix", "version", selfLinker)
|
||||
server := httptest.NewServer(handler)
|
||||
defer server.Close()
|
||||
|
||||
dontFollow := errors.New("don't follow")
|
||||
client := http.Client{
|
||||
CheckRedirect: func(req *http.Request, via []*http.Request) error {
|
||||
return dontFollow
|
||||
},
|
||||
}
|
||||
|
||||
table := []struct {
|
||||
id string
|
||||
err error
|
||||
code int
|
||||
}{
|
||||
{"cozy", nil, http.StatusTemporaryRedirect},
|
||||
{"horse", errors.New("no such id"), http.StatusInternalServerError},
|
||||
}
|
||||
|
||||
for _, item := range table {
|
||||
simpleStorage.errors["resourceLocation"] = item.err
|
||||
simpleStorage.resourceLocation = item.id
|
||||
resp, err := client.Get(server.URL + "/prefix/version/redirect/ns/other/foo/" + item.id)
|
||||
if resp == nil {
|
||||
t.Fatalf("Unexpected nil resp")
|
||||
}
|
||||
resp.Body.Close()
|
||||
if e, a := item.code, resp.StatusCode; e != a {
|
||||
t.Errorf("Expected %v, got %v", e, a)
|
||||
}
|
||||
if e, a := item.id, simpleStorage.requestedResourceLocationID; e != a {
|
||||
t.Errorf("Expected %v, got %v", e, a)
|
||||
}
|
||||
if item.err != nil {
|
||||
continue
|
||||
}
|
||||
if err == nil || err.(*url.Error).Err != dontFollow {
|
||||
t.Errorf("Unexpected err %#v", err)
|
||||
}
|
||||
if e, a := item.id, resp.Header.Get("Location"); e != a {
|
||||
t.Errorf("Expected %v, got %v", e, a)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -41,19 +41,19 @@ type RESTHandler struct {
|
||||
|
||||
// ServeHTTP handles requests to all RESTStorage objects.
|
||||
func (h *RESTHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
parts := splitPath(req.URL.Path)
|
||||
if len(parts) < 1 {
|
||||
namespace, kind, parts, err := KindAndNamespace(req)
|
||||
if err != nil {
|
||||
notFound(w, req)
|
||||
return
|
||||
}
|
||||
storage := h.storage[parts[0]]
|
||||
storage := h.storage[kind]
|
||||
if storage == nil {
|
||||
httplog.LogOf(req, w).Addf("'%v' has no storage object", parts[0])
|
||||
httplog.LogOf(req, w).Addf("'%v' has no storage object", kind)
|
||||
notFound(w, req)
|
||||
return
|
||||
}
|
||||
|
||||
h.handleRESTStorage(parts, req, w, storage)
|
||||
h.handleRESTStorage(parts, req, w, storage, namespace)
|
||||
}
|
||||
|
||||
// Sets the SelfLink field of the object.
|
||||
@ -66,12 +66,17 @@ func (h *RESTHandler) setSelfLink(obj runtime.Object, req *http.Request) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// TODO Remove this when namespace is in path
|
||||
|
||||
// we need to add namespace as a query param, if its not in the resource path
|
||||
if len(namespace) > 0 {
|
||||
query := newURL.Query()
|
||||
query.Set("namespace", namespace)
|
||||
newURL.RawQuery = query.Encode()
|
||||
parts := splitPath(req.URL.Path)
|
||||
if parts[0] != "ns" {
|
||||
query := newURL.Query()
|
||||
query.Set("namespace", namespace)
|
||||
newURL.RawQuery = query.Encode()
|
||||
}
|
||||
}
|
||||
|
||||
err = h.selfLinker.SetSelfLink(obj, newURL.String())
|
||||
if err != nil {
|
||||
return err
|
||||
@ -107,11 +112,14 @@ func (h *RESTHandler) setSelfLinkAddName(obj runtime.Object, req *http.Request)
|
||||
newURL.Path = path.Join(h.canonicalPrefix, req.URL.Path, name)
|
||||
newURL.RawQuery = ""
|
||||
newURL.Fragment = ""
|
||||
// TODO Remove this when namespace is in path
|
||||
// we need to add namespace as a query param, if its not in the resource path
|
||||
if len(namespace) > 0 {
|
||||
query := newURL.Query()
|
||||
query.Set("namespace", namespace)
|
||||
newURL.RawQuery = query.Encode()
|
||||
parts := splitPath(req.URL.Path)
|
||||
if parts[0] != "ns" {
|
||||
query := newURL.Query()
|
||||
query.Set("namespace", namespace)
|
||||
newURL.RawQuery = query.Encode()
|
||||
}
|
||||
}
|
||||
return h.selfLinker.SetSelfLink(obj, newURL.String())
|
||||
}
|
||||
@ -138,18 +146,10 @@ func curry(f func(runtime.Object, *http.Request) error, req *http.Request) func(
|
||||
// sync=[false|true] Synchronous request (only applies to create, update, delete operations)
|
||||
// timeout=<duration> Timeout for synchronous requests, only applies if sync=true
|
||||
// labels=<label-selector> Used for filtering list operations
|
||||
func (h *RESTHandler) handleRESTStorage(parts []string, req *http.Request, w http.ResponseWriter, storage RESTStorage) {
|
||||
ctx := api.NewContext()
|
||||
func (h *RESTHandler) handleRESTStorage(parts []string, req *http.Request, w http.ResponseWriter, storage RESTStorage, namespace string) {
|
||||
ctx := api.WithNamespace(api.NewContext(), namespace)
|
||||
sync := req.URL.Query().Get("sync") == "true"
|
||||
timeout := parseTimeout(req.URL.Query().Get("timeout"))
|
||||
// TODO for now, we pull namespace from query parameter, but according to spec, it must go in resource path in future PR
|
||||
// if a namespace if specified, it's always used.
|
||||
// for list/watch operations, a namespace is not required if omitted.
|
||||
// for all other operations, if namespace is omitted, we will default to default namespace.
|
||||
namespace := req.URL.Query().Get("namespace")
|
||||
if len(namespace) > 0 {
|
||||
ctx = api.WithNamespace(ctx, namespace)
|
||||
}
|
||||
switch req.Method {
|
||||
case "GET":
|
||||
switch len(parts) {
|
||||
@ -175,7 +175,7 @@ func (h *RESTHandler) handleRESTStorage(parts []string, req *http.Request, w htt
|
||||
}
|
||||
writeJSON(http.StatusOK, h.codec, list, w)
|
||||
case 2:
|
||||
item, err := storage.Get(api.WithNamespaceDefaultIfNone(ctx), parts[1])
|
||||
item, err := storage.Get(ctx, parts[1])
|
||||
if err != nil {
|
||||
errorJSON(err, h.codec, w)
|
||||
return
|
||||
@ -205,7 +205,7 @@ func (h *RESTHandler) handleRESTStorage(parts []string, req *http.Request, w htt
|
||||
errorJSON(err, h.codec, w)
|
||||
return
|
||||
}
|
||||
out, err := storage.Create(api.WithNamespaceDefaultIfNone(ctx), obj)
|
||||
out, err := storage.Create(ctx, obj)
|
||||
if err != nil {
|
||||
errorJSON(err, h.codec, w)
|
||||
return
|
||||
@ -218,7 +218,7 @@ func (h *RESTHandler) handleRESTStorage(parts []string, req *http.Request, w htt
|
||||
notFound(w, req)
|
||||
return
|
||||
}
|
||||
out, err := storage.Delete(api.WithNamespaceDefaultIfNone(ctx), parts[1])
|
||||
out, err := storage.Delete(ctx, parts[1])
|
||||
if err != nil {
|
||||
errorJSON(err, h.codec, w)
|
||||
return
|
||||
@ -242,7 +242,7 @@ func (h *RESTHandler) handleRESTStorage(parts []string, req *http.Request, w htt
|
||||
errorJSON(err, h.codec, w)
|
||||
return
|
||||
}
|
||||
out, err := storage.Update(api.WithNamespaceDefaultIfNone(ctx), obj)
|
||||
out, err := storage.Update(ctx, obj)
|
||||
if err != nil {
|
||||
errorJSON(err, h.codec, w)
|
||||
return
|
||||
|
@ -77,17 +77,19 @@ func isWebsocketRequest(req *http.Request) bool {
|
||||
|
||||
// ServeHTTP processes watch requests.
|
||||
func (h *WatchHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
ctx := api.NewContext()
|
||||
namespace := req.URL.Query().Get("namespace")
|
||||
if len(namespace) > 0 {
|
||||
ctx = api.WithNamespace(ctx, namespace)
|
||||
}
|
||||
parts := splitPath(req.URL.Path)
|
||||
if len(parts) < 1 || req.Method != "GET" {
|
||||
if req.Method != "GET" {
|
||||
notFound(w, req)
|
||||
return
|
||||
}
|
||||
storage := h.storage[parts[0]]
|
||||
|
||||
namespace, kind, _, err := KindAndNamespace(req)
|
||||
if err != nil {
|
||||
notFound(w, req)
|
||||
return
|
||||
}
|
||||
ctx := api.WithNamespace(api.NewContext(), namespace)
|
||||
|
||||
storage := h.storage[kind]
|
||||
if storage == nil {
|
||||
notFound(w, req)
|
||||
return
|
||||
|
@ -161,10 +161,18 @@ func (c *testClient) ValidateCommon(t *testing.T, err error) {
|
||||
}
|
||||
}
|
||||
|
||||
// convenience function to build paths
|
||||
func buildResourcePath(namespace, resource string) string {
|
||||
if len(namespace) > 0 {
|
||||
return path.Join("ns", namespace, resource)
|
||||
}
|
||||
return resource
|
||||
}
|
||||
|
||||
func TestListEmptyPods(t *testing.T) {
|
||||
ns := api.NamespaceDefault
|
||||
c := &testClient{
|
||||
Request: testRequest{Method: "GET", Path: "/pods"},
|
||||
Request: testRequest{Method: "GET", Path: buildResourcePath(ns, "/pods")},
|
||||
Response: Response{StatusCode: 200, Body: &api.PodList{}},
|
||||
}
|
||||
podList, err := c.Setup().Pods(ns).List(labels.Everything())
|
||||
@ -174,7 +182,7 @@ func TestListEmptyPods(t *testing.T) {
|
||||
func TestListPods(t *testing.T) {
|
||||
ns := api.NamespaceDefault
|
||||
c := &testClient{
|
||||
Request: testRequest{Method: "GET", Path: "/pods"},
|
||||
Request: testRequest{Method: "GET", Path: buildResourcePath(ns, "/pods")},
|
||||
Response: Response{StatusCode: 200,
|
||||
Body: &api.PodList{
|
||||
Items: []api.Pod{
|
||||
@ -206,7 +214,7 @@ func validateLabels(a, b string) bool {
|
||||
func TestListPodsLabels(t *testing.T) {
|
||||
ns := api.NamespaceDefault
|
||||
c := &testClient{
|
||||
Request: testRequest{Method: "GET", Path: "/pods", Query: url.Values{"labels": []string{"foo=bar,name=baz"}}},
|
||||
Request: testRequest{Method: "GET", Path: buildResourcePath(ns, "/pods"), Query: url.Values{"labels": []string{"foo=bar,name=baz"}}},
|
||||
Response: Response{
|
||||
StatusCode: 200,
|
||||
Body: &api.PodList{
|
||||
@ -236,7 +244,7 @@ func TestListPodsLabels(t *testing.T) {
|
||||
func TestGetPod(t *testing.T) {
|
||||
ns := api.NamespaceDefault
|
||||
c := &testClient{
|
||||
Request: testRequest{Method: "GET", Path: "/pods/foo"},
|
||||
Request: testRequest{Method: "GET", Path: buildResourcePath(ns, "/pods/foo")},
|
||||
Response: Response{
|
||||
StatusCode: 200,
|
||||
Body: &api.Pod{
|
||||
@ -268,15 +276,17 @@ func TestGetPodWithNoName(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestDeletePod(t *testing.T) {
|
||||
ns := api.NamespaceDefault
|
||||
c := &testClient{
|
||||
Request: testRequest{Method: "DELETE", Path: "/pods/foo"},
|
||||
Request: testRequest{Method: "DELETE", Path: buildResourcePath(ns, "/pods/foo")},
|
||||
Response: Response{StatusCode: 200},
|
||||
}
|
||||
err := c.Setup().Pods(api.NamespaceDefault).Delete("foo")
|
||||
err := c.Setup().Pods(ns).Delete("foo")
|
||||
c.Validate(t, nil, err)
|
||||
}
|
||||
|
||||
func TestCreatePod(t *testing.T) {
|
||||
ns := api.NamespaceDefault
|
||||
requestPod := &api.Pod{
|
||||
Status: api.PodStatus{
|
||||
Phase: api.PodRunning,
|
||||
@ -289,17 +299,18 @@ func TestCreatePod(t *testing.T) {
|
||||
},
|
||||
}
|
||||
c := &testClient{
|
||||
Request: testRequest{Method: "POST", Path: "/pods", Body: requestPod},
|
||||
Request: testRequest{Method: "POST", Path: buildResourcePath(ns, "/pods"), Body: requestPod},
|
||||
Response: Response{
|
||||
StatusCode: 200,
|
||||
Body: requestPod,
|
||||
},
|
||||
}
|
||||
receivedPod, err := c.Setup().Pods(api.NamespaceDefault).Create(requestPod)
|
||||
receivedPod, err := c.Setup().Pods(ns).Create(requestPod)
|
||||
c.Validate(t, receivedPod, err)
|
||||
}
|
||||
|
||||
func TestUpdatePod(t *testing.T) {
|
||||
ns := api.NamespaceDefault
|
||||
requestPod := &api.Pod{
|
||||
ObjectMeta: api.ObjectMeta{
|
||||
Name: "foo",
|
||||
@ -314,10 +325,10 @@ func TestUpdatePod(t *testing.T) {
|
||||
},
|
||||
}
|
||||
c := &testClient{
|
||||
Request: testRequest{Method: "PUT", Path: "/pods/foo"},
|
||||
Request: testRequest{Method: "PUT", Path: buildResourcePath(ns, "/pods/foo")},
|
||||
Response: Response{StatusCode: 200, Body: requestPod},
|
||||
}
|
||||
receivedPod, err := c.Setup().Pods(api.NamespaceDefault).Update(requestPod)
|
||||
receivedPod, err := c.Setup().Pods(ns).Update(requestPod)
|
||||
c.Validate(t, receivedPod, err)
|
||||
}
|
||||
|
||||
@ -350,8 +361,9 @@ func TestListControllers(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestGetController(t *testing.T) {
|
||||
ns := api.NamespaceDefault
|
||||
c := &testClient{
|
||||
Request: testRequest{Method: "GET", Path: "/replicationControllers/foo"},
|
||||
Request: testRequest{Method: "GET", Path: buildResourcePath(ns, "/replicationControllers/foo")},
|
||||
Response: Response{
|
||||
StatusCode: 200,
|
||||
Body: &api.ReplicationController{
|
||||
@ -369,7 +381,7 @@ func TestGetController(t *testing.T) {
|
||||
},
|
||||
},
|
||||
}
|
||||
receivedController, err := c.Setup().ReplicationControllers(api.NamespaceDefault).Get("foo")
|
||||
receivedController, err := c.Setup().ReplicationControllers(ns).Get("foo")
|
||||
c.Validate(t, receivedController, err)
|
||||
}
|
||||
|
||||
@ -385,11 +397,12 @@ func TestGetControllerWithNoName(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestUpdateController(t *testing.T) {
|
||||
ns := api.NamespaceDefault
|
||||
requestController := &api.ReplicationController{
|
||||
ObjectMeta: api.ObjectMeta{Name: "foo", ResourceVersion: "1"},
|
||||
}
|
||||
c := &testClient{
|
||||
Request: testRequest{Method: "PUT", Path: "/replicationControllers/foo"},
|
||||
Request: testRequest{Method: "PUT", Path: buildResourcePath(ns, "/replicationControllers/foo")},
|
||||
Response: Response{
|
||||
StatusCode: 200,
|
||||
Body: &api.ReplicationController{
|
||||
@ -407,25 +420,27 @@ func TestUpdateController(t *testing.T) {
|
||||
},
|
||||
},
|
||||
}
|
||||
receivedController, err := c.Setup().ReplicationControllers(api.NamespaceDefault).Update(requestController)
|
||||
receivedController, err := c.Setup().ReplicationControllers(ns).Update(requestController)
|
||||
c.Validate(t, receivedController, err)
|
||||
}
|
||||
|
||||
func TestDeleteController(t *testing.T) {
|
||||
ns := api.NamespaceDefault
|
||||
c := &testClient{
|
||||
Request: testRequest{Method: "DELETE", Path: "/replicationControllers/foo"},
|
||||
Request: testRequest{Method: "DELETE", Path: buildResourcePath(ns, "/replicationControllers/foo")},
|
||||
Response: Response{StatusCode: 200},
|
||||
}
|
||||
err := c.Setup().ReplicationControllers(api.NamespaceDefault).Delete("foo")
|
||||
err := c.Setup().ReplicationControllers(ns).Delete("foo")
|
||||
c.Validate(t, nil, err)
|
||||
}
|
||||
|
||||
func TestCreateController(t *testing.T) {
|
||||
ns := api.NamespaceDefault
|
||||
requestController := &api.ReplicationController{
|
||||
ObjectMeta: api.ObjectMeta{Name: "foo"},
|
||||
}
|
||||
c := &testClient{
|
||||
Request: testRequest{Method: "POST", Path: "/replicationControllers", Body: requestController},
|
||||
Request: testRequest{Method: "POST", Path: buildResourcePath(ns, "/replicationControllers"), Body: requestController},
|
||||
Response: Response{
|
||||
StatusCode: 200,
|
||||
Body: &api.ReplicationController{
|
||||
@ -443,7 +458,7 @@ func TestCreateController(t *testing.T) {
|
||||
},
|
||||
},
|
||||
}
|
||||
receivedController, err := c.Setup().ReplicationControllers(api.NamespaceDefault).Create(requestController)
|
||||
receivedController, err := c.Setup().ReplicationControllers(ns).Create(requestController)
|
||||
c.Validate(t, receivedController, err)
|
||||
}
|
||||
|
||||
@ -457,8 +472,9 @@ func body(obj runtime.Object, raw *string) *string {
|
||||
}
|
||||
|
||||
func TestListServices(t *testing.T) {
|
||||
ns := api.NamespaceDefault
|
||||
c := &testClient{
|
||||
Request: testRequest{Method: "GET", Path: "/services"},
|
||||
Request: testRequest{Method: "GET", Path: buildResourcePath(ns, "/services")},
|
||||
Response: Response{StatusCode: 200,
|
||||
Body: &api.ServiceList{
|
||||
Items: []api.Service{
|
||||
@ -480,14 +496,15 @@ func TestListServices(t *testing.T) {
|
||||
},
|
||||
},
|
||||
}
|
||||
receivedServiceList, err := c.Setup().Services(api.NamespaceDefault).List(labels.Everything())
|
||||
receivedServiceList, err := c.Setup().Services(ns).List(labels.Everything())
|
||||
t.Logf("received services: %v %#v", err, receivedServiceList)
|
||||
c.Validate(t, receivedServiceList, err)
|
||||
}
|
||||
|
||||
func TestListServicesLabels(t *testing.T) {
|
||||
ns := api.NamespaceDefault
|
||||
c := &testClient{
|
||||
Request: testRequest{Method: "GET", Path: "/services", Query: url.Values{"labels": []string{"foo=bar,name=baz"}}},
|
||||
Request: testRequest{Method: "GET", Path: buildResourcePath(ns, "/services"), Query: url.Values{"labels": []string{"foo=bar,name=baz"}}},
|
||||
Response: Response{StatusCode: 200,
|
||||
Body: &api.ServiceList{
|
||||
Items: []api.Service{
|
||||
@ -512,16 +529,17 @@ func TestListServicesLabels(t *testing.T) {
|
||||
c.Setup()
|
||||
c.QueryValidator["labels"] = validateLabels
|
||||
selector := labels.Set{"foo": "bar", "name": "baz"}.AsSelector()
|
||||
receivedServiceList, err := c.Services(api.NamespaceDefault).List(selector)
|
||||
receivedServiceList, err := c.Services(ns).List(selector)
|
||||
c.Validate(t, receivedServiceList, err)
|
||||
}
|
||||
|
||||
func TestGetService(t *testing.T) {
|
||||
ns := api.NamespaceDefault
|
||||
c := &testClient{
|
||||
Request: testRequest{Method: "GET", Path: "/services/1"},
|
||||
Request: testRequest{Method: "GET", Path: buildResourcePath(ns, "/services/1")},
|
||||
Response: Response{StatusCode: 200, Body: &api.Service{ObjectMeta: api.ObjectMeta{Name: "service-1"}}},
|
||||
}
|
||||
response, err := c.Setup().Services(api.NamespaceDefault).Get("1")
|
||||
response, err := c.Setup().Services(ns).Get("1")
|
||||
c.Validate(t, response, err)
|
||||
}
|
||||
|
||||
@ -537,36 +555,40 @@ func TestGetServiceWithNoName(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestCreateService(t *testing.T) {
|
||||
ns := api.NamespaceDefault
|
||||
c := &testClient{
|
||||
Request: testRequest{Method: "POST", Path: "/services", Body: &api.Service{ObjectMeta: api.ObjectMeta{Name: "service-1"}}},
|
||||
Request: testRequest{Method: "POST", Path: buildResourcePath(ns, "/services"), Body: &api.Service{ObjectMeta: api.ObjectMeta{Name: "service-1"}}},
|
||||
Response: Response{StatusCode: 200, Body: &api.Service{ObjectMeta: api.ObjectMeta{Name: "service-1"}}},
|
||||
}
|
||||
response, err := c.Setup().Services(api.NamespaceDefault).Create(&api.Service{ObjectMeta: api.ObjectMeta{Name: "service-1"}})
|
||||
response, err := c.Setup().Services(ns).Create(&api.Service{ObjectMeta: api.ObjectMeta{Name: "service-1"}})
|
||||
c.Validate(t, response, err)
|
||||
}
|
||||
|
||||
func TestUpdateService(t *testing.T) {
|
||||
ns := api.NamespaceDefault
|
||||
svc := &api.Service{ObjectMeta: api.ObjectMeta{Name: "service-1", ResourceVersion: "1"}}
|
||||
c := &testClient{
|
||||
Request: testRequest{Method: "PUT", Path: "/services/service-1", Body: svc},
|
||||
Request: testRequest{Method: "PUT", Path: buildResourcePath(ns, "/services/service-1"), Body: svc},
|
||||
Response: Response{StatusCode: 200, Body: svc},
|
||||
}
|
||||
response, err := c.Setup().Services(api.NamespaceDefault).Update(svc)
|
||||
response, err := c.Setup().Services(ns).Update(svc)
|
||||
c.Validate(t, response, err)
|
||||
}
|
||||
|
||||
func TestDeleteService(t *testing.T) {
|
||||
ns := api.NamespaceDefault
|
||||
c := &testClient{
|
||||
Request: testRequest{Method: "DELETE", Path: "/services/1"},
|
||||
Request: testRequest{Method: "DELETE", Path: buildResourcePath(ns, "/services/1")},
|
||||
Response: Response{StatusCode: 200},
|
||||
}
|
||||
err := c.Setup().Services(api.NamespaceDefault).Delete("1")
|
||||
err := c.Setup().Services(ns).Delete("1")
|
||||
c.Validate(t, nil, err)
|
||||
}
|
||||
|
||||
func TestListEndpooints(t *testing.T) {
|
||||
ns := api.NamespaceDefault
|
||||
c := &testClient{
|
||||
Request: testRequest{Method: "GET", Path: "/endpoints"},
|
||||
Request: testRequest{Method: "GET", Path: buildResourcePath(ns, "/endpoints")},
|
||||
Response: Response{StatusCode: 200,
|
||||
Body: &api.EndpointsList{
|
||||
Items: []api.Endpoints{
|
||||
@ -578,16 +600,17 @@ func TestListEndpooints(t *testing.T) {
|
||||
},
|
||||
},
|
||||
}
|
||||
receivedEndpointsList, err := c.Setup().Endpoints(api.NamespaceDefault).List(labels.Everything())
|
||||
receivedEndpointsList, err := c.Setup().Endpoints(ns).List(labels.Everything())
|
||||
c.Validate(t, receivedEndpointsList, err)
|
||||
}
|
||||
|
||||
func TestGetEndpoints(t *testing.T) {
|
||||
ns := api.NamespaceDefault
|
||||
c := &testClient{
|
||||
Request: testRequest{Method: "GET", Path: "/endpoints/endpoint-1"},
|
||||
Request: testRequest{Method: "GET", Path: buildResourcePath(ns, "/endpoints/endpoint-1")},
|
||||
Response: Response{StatusCode: 200, Body: &api.Endpoints{ObjectMeta: api.ObjectMeta{Name: "endpoint-1"}}},
|
||||
}
|
||||
response, err := c.Setup().Endpoints(api.NamespaceDefault).Get("endpoint-1")
|
||||
response, err := c.Setup().Endpoints(ns).Get("endpoint-1")
|
||||
c.Validate(t, response, err)
|
||||
}
|
||||
|
||||
|
@ -78,8 +78,8 @@ func (c *endpoints) Get(name string) (result *api.Endpoints, err error) {
|
||||
// Watch returns a watch.Interface that watches the requested endpoints for a service.
|
||||
func (c *endpoints) Watch(label, field labels.Selector, resourceVersion string) (watch.Interface, error) {
|
||||
return c.r.Get().
|
||||
Namespace(c.ns).
|
||||
Path("watch").
|
||||
Namespace(c.ns).
|
||||
Path("endpoints").
|
||||
Param("resourceVersion", resourceVersion).
|
||||
SelectorParam("labels", label).
|
||||
|
@ -65,8 +65,8 @@ func (e *events) Create(event *api.Event) (*api.Event, error) {
|
||||
}
|
||||
result := &api.Event{}
|
||||
err := e.client.Post().
|
||||
Path("events").
|
||||
Namespace(event.Namespace).
|
||||
Path("events").
|
||||
Body(event).
|
||||
Do().
|
||||
Into(result)
|
||||
@ -77,8 +77,8 @@ func (e *events) Create(event *api.Event) (*api.Event, error) {
|
||||
func (e *events) List(label, field labels.Selector) (*api.EventList, error) {
|
||||
result := &api.EventList{}
|
||||
err := e.client.Get().
|
||||
Path("events").
|
||||
Namespace(e.namespace).
|
||||
Path("events").
|
||||
SelectorParam("labels", label).
|
||||
SelectorParam("fields", field).
|
||||
Do().
|
||||
@ -94,9 +94,9 @@ func (e *events) Get(name string) (*api.Event, error) {
|
||||
|
||||
result := &api.Event{}
|
||||
err := e.client.Get().
|
||||
Namespace(e.namespace).
|
||||
Path("events").
|
||||
Path(name).
|
||||
Namespace(e.namespace).
|
||||
Do().
|
||||
Into(result)
|
||||
return result, err
|
||||
@ -106,9 +106,9 @@ func (e *events) Get(name string) (*api.Event, error) {
|
||||
func (e *events) Watch(label, field labels.Selector, resourceVersion string) (watch.Interface, error) {
|
||||
return e.client.Get().
|
||||
Path("watch").
|
||||
Namespace(e.namespace).
|
||||
Path("events").
|
||||
Param("resourceVersion", resourceVersion).
|
||||
Namespace(e.namespace).
|
||||
SelectorParam("labels", label).
|
||||
SelectorParam("fields", field).
|
||||
Watch()
|
||||
|
@ -95,8 +95,8 @@ func (c *replicationControllers) Delete(name string) error {
|
||||
// Watch returns a watch.Interface that watches the requested controllers.
|
||||
func (c *replicationControllers) Watch(label, field labels.Selector, resourceVersion string) (watch.Interface, error) {
|
||||
return c.r.Get().
|
||||
Namespace(c.ns).
|
||||
Path("watch").
|
||||
Namespace(c.ns).
|
||||
Path("replicationControllers").
|
||||
Param("resourceVersion", resourceVersion).
|
||||
SelectorParam("labels", label).
|
||||
|
@ -136,7 +136,7 @@ func (r *Request) Namespace(namespace string) *Request {
|
||||
return r
|
||||
}
|
||||
if len(namespace) > 0 {
|
||||
return r.setParam("namespace", namespace)
|
||||
return r.Path("ns").Path(namespace)
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
@ -95,8 +95,8 @@ func (c *services) Delete(name string) error {
|
||||
// Watch returns a watch.Interface that watches the requested services.
|
||||
func (c *services) Watch(label, field labels.Selector, resourceVersion string) (watch.Interface, error) {
|
||||
return c.r.Get().
|
||||
Namespace(c.ns).
|
||||
Path("watch").
|
||||
Namespace(c.ns).
|
||||
Path("services").
|
||||
Param("resourceVersion", resourceVersion).
|
||||
SelectorParam("labels", label).
|
||||
|
@ -221,7 +221,7 @@ func TestCreateReplica(t *testing.T) {
|
||||
},
|
||||
Spec: controllerSpec.Spec.Template.Spec,
|
||||
}
|
||||
fakeHandler.ValidateRequest(t, makeURL("/pods?namespace=default"), "POST", nil)
|
||||
fakeHandler.ValidateRequest(t, makeURL("/ns/default/pods"), "POST", nil)
|
||||
actualPod, err := client.Codec.Decode([]byte(fakeHandler.RequestBody))
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error: %#v", err)
|
||||
|
@ -47,7 +47,7 @@ func NewRESTHelper(client RESTClient, mapping *meta.RESTMapping) *RESTHelper {
|
||||
}
|
||||
|
||||
func (m *RESTHelper) Get(namespace, name string, selector labels.Selector) (runtime.Object, error) {
|
||||
return m.RESTClient.Get().Path(m.Resource).Namespace(namespace).Path(name).SelectorParam("labels", selector).Do().Get()
|
||||
return m.RESTClient.Get().Namespace(namespace).Path(m.Resource).Path(name).SelectorParam("labels", selector).Do().Get()
|
||||
}
|
||||
|
||||
func (m *RESTHelper) List(namespace string, selector labels.Selector) (runtime.Object, error) {
|
||||
@ -57,8 +57,8 @@ func (m *RESTHelper) List(namespace string, selector labels.Selector) (runtime.O
|
||||
func (m *RESTHelper) Watch(namespace, resourceVersion string, labelSelector, fieldSelector labels.Selector) (watch.Interface, error) {
|
||||
return m.RESTClient.Get().
|
||||
Path("watch").
|
||||
Path(m.Resource).
|
||||
Namespace(namespace).
|
||||
Path(m.Resource).
|
||||
Param("resourceVersion", resourceVersion).
|
||||
SelectorParam("labels", labelSelector).
|
||||
SelectorParam("fields", fieldSelector).
|
||||
@ -66,7 +66,7 @@ func (m *RESTHelper) Watch(namespace, resourceVersion string, labelSelector, fie
|
||||
}
|
||||
|
||||
func (m *RESTHelper) Delete(namespace, name string) error {
|
||||
return m.RESTClient.Delete().Path(m.Resource).Namespace(namespace).Path(name).Do().Error()
|
||||
return m.RESTClient.Delete().Namespace(namespace).Path(m.Resource).Path(name).Do().Error()
|
||||
}
|
||||
|
||||
func (m *RESTHelper) Create(namespace string, modify bool, data []byte) error {
|
||||
@ -99,7 +99,7 @@ func (m *RESTHelper) Create(namespace string, modify bool, data []byte) error {
|
||||
}
|
||||
|
||||
func createResource(c RESTClient, resourcePath, namespace string, data []byte) error {
|
||||
return c.Post().Path(resourcePath).Namespace(namespace).Body(data).Do().Error()
|
||||
return c.Post().Namespace(namespace).Path(resourcePath).Body(data).Do().Error()
|
||||
}
|
||||
|
||||
func (m *RESTHelper) Update(namespace, name string, overwrite bool, data []byte) error {
|
||||
@ -142,5 +142,5 @@ func (m *RESTHelper) Update(namespace, name string, overwrite bool, data []byte)
|
||||
}
|
||||
|
||||
func updateResource(c RESTClient, resourcePath, namespace, name string, data []byte) error {
|
||||
return c.Put().Path(resourcePath).Namespace(namespace).Path(name).Body(data).Do().Error()
|
||||
return c.Put().Namespace(namespace).Path(resourcePath).Path(name).Body(data).Do().Error()
|
||||
}
|
||||
|
@ -37,6 +37,15 @@ func objBody(obj runtime.Object) io.ReadCloser {
|
||||
return ioutil.NopCloser(bytes.NewReader([]byte(runtime.EncodeOrDie(testapi.Codec(), obj))))
|
||||
}
|
||||
|
||||
// splitPath returns the segments for a URL path.
|
||||
func splitPath(path string) []string {
|
||||
path = strings.Trim(path, "/")
|
||||
if path == "" {
|
||||
return []string{}
|
||||
}
|
||||
return strings.Split(path, "/")
|
||||
}
|
||||
|
||||
func TestRESTHelperDelete(t *testing.T) {
|
||||
tests := []struct {
|
||||
Err bool
|
||||
@ -65,12 +74,13 @@ func TestRESTHelperDelete(t *testing.T) {
|
||||
t.Errorf("unexpected method: %#v", req)
|
||||
return false
|
||||
}
|
||||
if !strings.HasSuffix(req.URL.Path, "/foo") {
|
||||
t.Errorf("url doesn't contain name: %#v", req)
|
||||
parts := splitPath(req.URL.Path)
|
||||
if parts[1] != "bar" {
|
||||
t.Errorf("url doesn't contain namespace: %#v", req)
|
||||
return false
|
||||
}
|
||||
if req.URL.Query().Get("namespace") != "bar" {
|
||||
t.Errorf("url doesn't contain namespace: %#v", req)
|
||||
if parts[2] != "foo" {
|
||||
t.Errorf("url doesn't contain name: %#v", req)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
@ -105,7 +115,8 @@ func TestRESTHelperCreate(t *testing.T) {
|
||||
t.Errorf("unexpected method: %#v", req)
|
||||
return false
|
||||
}
|
||||
if req.URL.Query().Get("namespace") != "bar" {
|
||||
parts := splitPath(req.URL.Path)
|
||||
if parts[1] != "bar" {
|
||||
t.Errorf("url doesn't contain namespace: %#v", req)
|
||||
return false
|
||||
}
|
||||
@ -230,12 +241,13 @@ func TestRESTHelperGet(t *testing.T) {
|
||||
t.Errorf("unexpected method: %#v", req)
|
||||
return false
|
||||
}
|
||||
if !strings.HasSuffix(req.URL.Path, "/foo") {
|
||||
t.Errorf("url doesn't contain name: %#v", req)
|
||||
parts := splitPath(req.URL.Path)
|
||||
if parts[1] != "bar" {
|
||||
t.Errorf("url doesn't contain namespace: %#v", req)
|
||||
return false
|
||||
}
|
||||
if req.URL.Query().Get("namespace") != "bar" {
|
||||
t.Errorf("url doesn't contain namespace: %#v", req)
|
||||
if parts[2] != "foo" {
|
||||
t.Errorf("url doesn't contain name: %#v", req)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
@ -273,12 +285,13 @@ func TestRESTHelperUpdate(t *testing.T) {
|
||||
t.Errorf("unexpected method: %#v", req)
|
||||
return false
|
||||
}
|
||||
if !strings.HasSuffix(req.URL.Path, "/foo") {
|
||||
t.Errorf("url doesn't contain name: %#v", req)
|
||||
parts := splitPath(req.URL.Path)
|
||||
if parts[1] != "bar" {
|
||||
t.Errorf("url doesn't contain namespace: %#v", req)
|
||||
return false
|
||||
}
|
||||
if req.URL.Query().Get("namespace") != "bar" {
|
||||
t.Errorf("url doesn't contain namespace: %#v", req)
|
||||
if parts[2] != "foo" {
|
||||
t.Errorf("url doesn't contain name: %#v", req)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
|
@ -192,7 +192,7 @@ func TestDefaultErrorFunc(t *testing.T) {
|
||||
}
|
||||
mux := http.NewServeMux()
|
||||
// FakeHandler musn't be sent requests other than the one you want to test.
|
||||
mux.Handle("/api/"+testapi.Version()+"/pods/foo", &handler)
|
||||
mux.Handle("/api/"+testapi.Version()+"/ns/bar/pods/foo", &handler)
|
||||
server := httptest.NewServer(mux)
|
||||
defer server.Close()
|
||||
factory := NewConfigFactory(client.NewOrDie(&client.Config{Host: server.URL, Version: testapi.Version()}))
|
||||
@ -213,7 +213,7 @@ func TestDefaultErrorFunc(t *testing.T) {
|
||||
if !exists {
|
||||
continue
|
||||
}
|
||||
handler.ValidateRequest(t, "/api/"+testapi.Version()+"/pods/foo?namespace=bar", "GET", nil)
|
||||
handler.ValidateRequest(t, "/api/"+testapi.Version()+"/ns/bar/pods/foo", "GET", nil)
|
||||
if e, a := testPod, got; !reflect.DeepEqual(e, a) {
|
||||
t.Errorf("Expected %v, got %v", e, a)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user