diff --git a/pkg/kubectl/cmd/proxy.go b/pkg/kubectl/cmd/proxy.go index 234e0f5332b..5b607f4f383 100644 --- a/pkg/kubectl/cmd/proxy.go +++ b/pkg/kubectl/cmd/proxy.go @@ -38,9 +38,24 @@ $ kubectl proxy --api-prefix=/k8s-api` func NewCmdProxy(f *cmdutil.Factory, out io.Writer) *cobra.Command { cmd := &cobra.Command{ - Use: "proxy [--port=PORT] [--www=static-dir] [--www-prefix=prefix] [--api-prefix=prefix]", - Short: "Run a proxy to the Kubernetes API server", - Long: `Run a proxy to the Kubernetes API server. `, + Use: "proxy [--port=PORT] [--www=static-dir] [--www-prefix=prefix] [--api-prefix=prefix]", + Short: "Run a proxy to the Kubernetes API server", + Long: `To proxy all of the kubernetes api and nothing else, use: + +kubectl proxy --api-prefix=/ + +To proxy only part of the kubernetes api and also some static files: + +kubectl proxy --www=/my/files --www-prefix=/static/ --api-prefix=/api/ + +The above lets you 'curl localhost:8001/api/v1/pods'. + +To proxy the entire kubernetes api at a different root, use: + +kubectl proxy --api-prefix=/custom/ + +The above lets you 'curl localhost:8001/custom/api/v1/pods' +`, Example: proxy_example, Run: func(cmd *cobra.Command, args []string) { err := RunProxy(f, out, cmd) diff --git a/pkg/kubectl/proxy_server.go b/pkg/kubectl/proxy_server.go index f307c0f2b52..63f58c530c5 100644 --- a/pkg/kubectl/proxy_server.go +++ b/pkg/kubectl/proxy_server.go @@ -28,17 +28,18 @@ import ( // ProxyServer is a http.Handler which proxies Kubernetes APIs to remote API server. type ProxyServer struct { + mux *http.ServeMux httputil.ReverseProxy } // NewProxyServer creates and installs a new ProxyServer. // It automatically registers the created ProxyServer to http.DefaultServeMux. func NewProxyServer(filebase string, apiProxyPrefix string, staticPrefix string, cfg *client.Config) (*ProxyServer, error) { - prefix := cfg.Prefix - if prefix == "" { - prefix = "/api" + host := cfg.Host + if !strings.HasSuffix(host, "/") { + host = host + "/" } - target, err := url.Parse(singleJoiningSlash(cfg.Host, prefix)) + target, err := url.Parse(host) if err != nil { return nil, err } @@ -46,15 +47,22 @@ func NewProxyServer(filebase string, apiProxyPrefix string, staticPrefix string, if proxy.Transport, err = client.TransportFor(cfg); err != nil { return nil, err } - http.Handle(apiProxyPrefix, http.StripPrefix(apiProxyPrefix, proxy)) - http.Handle(staticPrefix, newFileHandler(staticPrefix, filebase)) + if strings.HasPrefix(apiProxyPrefix, "/api") { + proxy.mux.Handle(apiProxyPrefix, proxy) + } else { + proxy.mux.Handle(apiProxyPrefix, http.StripPrefix(apiProxyPrefix, proxy)) + } + proxy.mux.Handle(staticPrefix, newFileHandler(staticPrefix, filebase)) return proxy, nil } // Serve starts the server (http.DefaultServeMux) on given port, loops forever. func (s *ProxyServer) Serve(port int) error { - addr := fmt.Sprintf(":%d", port) - return http.ListenAndServe(addr, nil) + server := http.Server{ + Addr: fmt.Sprintf(":%d", port), + Handler: s.mux, + } + return server.ListenAndServe() } func newProxyServer(target *url.URL) *ProxyServer { @@ -63,7 +71,10 @@ func newProxyServer(target *url.URL) *ProxyServer { req.URL.Host = target.Host req.URL.Path = singleJoiningSlash(target.Path, req.URL.Path) } - return &ProxyServer{ReverseProxy: httputil.ReverseProxy{Director: director}} + return &ProxyServer{ + ReverseProxy: httputil.ReverseProxy{Director: director}, + mux: http.NewServeMux(), + } } func newFileHandler(prefix, base string) http.Handler { diff --git a/pkg/kubectl/proxy_server_test.go b/pkg/kubectl/proxy_server_test.go index ad9fdd7a7e9..a8e9dc213de 100644 --- a/pkg/kubectl/proxy_server_test.go +++ b/pkg/kubectl/proxy_server_test.go @@ -25,6 +25,8 @@ import ( "path/filepath" "strings" "testing" + + "github.com/GoogleCloudPlatform/kubernetes/pkg/client" ) func TestFileServing(t *testing.T) { @@ -104,3 +106,55 @@ func TestAPIRequests(t *testing.T) { } } } + +func TestPathHandling(t *testing.T) { + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + fmt.Fprint(w, r.URL.Path) + })) + defer ts.Close() + + table := []struct { + prefix string + reqPath string + expectPath string + }{ + {"/api/", "/metrics", "404 page not found\n"}, + {"/api/", "/api/metrics", "/api/metrics"}, + {"/api/", "/api/v1/pods/", "/api/v1/pods/"}, + {"/", "/metrics", "/metrics"}, + {"/", "/api/v1/pods/", "/api/v1/pods/"}, + {"/custom/", "/metrics", "404 page not found\n"}, + {"/custom/", "/api/metrics", "404 page not found\n"}, + {"/custom/", "/api/v1/pods/", "404 page not found\n"}, + {"/custom/", "/custom/api/metrics", "/api/metrics"}, + {"/custom/", "/custom/api/v1/pods/", "/api/v1/pods/"}, + } + + cc := &client.Config{ + Host: ts.URL, + } + + for _, item := range table { + func() { + p, err := NewProxyServer("", item.prefix, "/not/used/for/this/test", cc) + if err != nil { + t.Fatalf("%#v: %v", item, err) + } + pts := httptest.NewServer(p.mux) + defer pts.Close() + + r, err := http.Get(pts.URL + item.reqPath) + if err != nil { + t.Fatalf("%#v: %v", item, err) + } + body, err := ioutil.ReadAll(r.Body) + r.Body.Close() + if err != nil { + t.Fatalf("%#v: %v", item, err) + } + if e, a := item.expectPath, string(body); e != a { + t.Errorf("%#v: Wanted %q, got %q", item, e, a) + } + }() + } +}