From ce4e5ac01d638e30b32d6cd72d1bee8435d3124d Mon Sep 17 00:00:00 2001 From: Steven Zou Date: Tue, 3 Jul 2018 21:36:20 +0800 Subject: [PATCH] Implement the related handler methods of the transparent operations based on proxy support index file support object downloading support health checking support related manipulating operations except getting chart version --- src/chartserver/base_handler.go | 7 +- src/chartserver/controller.go | 5 +- src/chartserver/manipulation_handler.go | 28 ++++-- src/chartserver/repo_handler.go | 13 ++- src/chartserver/reverse_proxy.go | 115 ++++++++++++++++++++++++ 5 files changed, 150 insertions(+), 18 deletions(-) create mode 100644 src/chartserver/reverse_proxy.go diff --git a/src/chartserver/base_handler.go b/src/chartserver/base_handler.go index f8a088d37..b3df173ae 100644 --- a/src/chartserver/base_handler.go +++ b/src/chartserver/base_handler.go @@ -2,15 +2,16 @@ package chartserver import ( "net/http" - "net/http/httputil" ) //BaseHandler defines the handlers related with the chart server itself. type BaseHandler struct { //Proxy used to to transfer the traffic of requests //It's mainly used to talk to the backend chart server - trafficProxy *httputil.ReverseProxy + trafficProxy *ProxyEngine } //GetHealthStatus will return the health status of the backend chart repository server -func (bh *BaseHandler) GetHealthStatus(w http.ResponseWriter, req *http.Request) {} +func (bh *BaseHandler) GetHealthStatus(w http.ResponseWriter, req *http.Request) { + bh.trafficProxy.ServeHTTP(w, req) +} diff --git a/src/chartserver/controller.go b/src/chartserver/controller.go index 84b7cdf18..37314780e 100644 --- a/src/chartserver/controller.go +++ b/src/chartserver/controller.go @@ -2,7 +2,6 @@ package chartserver import ( "errors" - "net/http/httputil" "net/url" ) @@ -29,8 +28,8 @@ func NewController(backendServer *url.URL) (*Controller, error) { return nil, errors.New("failed to create chartserver.Controller: backend sever address is required") } - //Currently, no customization requirements needed, so let's use the simple proxy here now - proxy := httputil.NewSingleHostReverseProxy(backendServer) + //Use customized reverse proxy + proxy := NewProxyEngine(backendServer) //Initialize chart operator for use operator := &ChartOperator{} diff --git a/src/chartserver/manipulation_handler.go b/src/chartserver/manipulation_handler.go index 207bf9be8..37b212d71 100644 --- a/src/chartserver/manipulation_handler.go +++ b/src/chartserver/manipulation_handler.go @@ -2,7 +2,6 @@ package chartserver import ( "net/http" - "net/http/httputil" ) //ManipulationHandler includes all the handler methods for the purpose of manipulating the @@ -10,28 +9,41 @@ import ( type ManipulationHandler struct { //Proxy used to to transfer the traffic of requests //It's mainly used to talk to the backend chart server - trafficProxy *httputil.ReverseProxy + trafficProxy *ProxyEngine //Parse and process the chart version to provide required info data chartOperator *ChartOperator } //ListCharts lists all the charts under the specified namespace -func (mh *ManipulationHandler) ListCharts(w http.ResponseWriter, req *http.Request) {} +func (mh *ManipulationHandler) ListCharts(w http.ResponseWriter, req *http.Request) { + mh.trafficProxy.ServeHTTP(w, req) +} //GetChart returns all the chart versions under the specified chart -func (mh *ManipulationHandler) GetChart(w http.ResponseWriter, req *http.Request) {} +func (mh *ManipulationHandler) GetChart(w http.ResponseWriter, req *http.Request) { + mh.trafficProxy.ServeHTTP(w, req) +} //GetChartVersion get the specified version for one chart //This handler should return the details of the chart version, //maybe including metadata,dependencies and values etc. -func (mh *ManipulationHandler) GetChartVersion(w http.ResponseWriter, req *http.Request) {} +func (mh *ManipulationHandler) GetChartVersion(w http.ResponseWriter, req *http.Request) { + w.WriteHeader(http.StatusNotImplemented) + w.Write([]byte("not implemented")) +} //UploadChartVersion will save the new version of the chart to the backend storage -func (mh *ManipulationHandler) UploadChartVersion(w http.ResponseWriter, req *http.Request) {} +func (mh *ManipulationHandler) UploadChartVersion(w http.ResponseWriter, req *http.Request) { + mh.trafficProxy.ServeHTTP(w, req) +} //UploadProvenanceFile will save the provenance file of the chart to the backend storage -func (mh *ManipulationHandler) UploadProvenanceFile(w http.ResponseWriter, req *http.Request) {} +func (mh *ManipulationHandler) UploadProvenanceFile(w http.ResponseWriter, req *http.Request) { + mh.trafficProxy.ServeHTTP(w, req) +} //DeleteChartVersion will delete the specified version of the chart -func (mh *ManipulationHandler) DeleteChartVersion(w http.ResponseWriter, req *http.Request) {} +func (mh *ManipulationHandler) DeleteChartVersion(w http.ResponseWriter, req *http.Request) { + mh.trafficProxy.ServeHTTP(w, req) +} diff --git a/src/chartserver/repo_handler.go b/src/chartserver/repo_handler.go index 37a6f7561..50a2abe5e 100644 --- a/src/chartserver/repo_handler.go +++ b/src/chartserver/repo_handler.go @@ -2,7 +2,6 @@ package chartserver import ( "net/http" - "net/http/httputil" ) //RepositoryHandler defines all the handlers to handle the requests related with chart repository @@ -10,18 +9,24 @@ import ( type RepositoryHandler struct { //Proxy used to to transfer the traffic of requests //It's mainly used to talk to the backend chart server - trafficProxy *httputil.ReverseProxy + trafficProxy *ProxyEngine } //GetIndexFileWithNS will read the index.yaml data under the specified namespace func (rh *RepositoryHandler) GetIndexFileWithNS(w http.ResponseWriter, req *http.Request) { + rh.trafficProxy.ServeHTTP(w, req) } //GetIndexFile will read the index.yaml under all namespaces and merge them as a single one //Please be aware that, to support this function, the backend chart repository server should //enable multi-tenancies -func (rh *RepositoryHandler) GetIndexFile(w http.ResponseWriter, req *http.Request) {} +func (rh *RepositoryHandler) GetIndexFile(w http.ResponseWriter, req *http.Request) { + w.WriteHeader(http.StatusNotImplemented) + w.Write([]byte("not implemented")) +} //DownloadChartObject will download the stored chart object to the client //e.g: helm install -func (rh *RepositoryHandler) DownloadChartObject(w http.ResponseWriter, req *http.Request) {} +func (rh *RepositoryHandler) DownloadChartObject(w http.ResponseWriter, req *http.Request) { + rh.trafficProxy.ServeHTTP(w, req) +} diff --git a/src/chartserver/reverse_proxy.go b/src/chartserver/reverse_proxy.go new file mode 100644 index 000000000..be4bcba0e --- /dev/null +++ b/src/chartserver/reverse_proxy.go @@ -0,0 +1,115 @@ +package chartserver + +import ( + "bytes" + "encoding/json" + "io/ioutil" + "log" + "net/http" + "net/http/httputil" + "net/url" + "os" + "strconv" + "strings" +) + +const ( + userName = "chart_controller" + passwordKey = "UI_SECRET" + agentHarbor = "HARBOR" + authHeader = "Authorization" + contentLengthHeader = "Content-Length" +) + +//ProxyEngine is used to proxy the related traffics +type ProxyEngine struct { + //The backend target server the traffic will be forwarded to + //Just in case we'll use it + backend *url.URL + + //Use go reverse proxy as engine + engine *httputil.ReverseProxy +} + +//NewProxyEngine is constructor of NewProxyEngine +func NewProxyEngine(target *url.URL) *ProxyEngine { + return &ProxyEngine{ + backend: target, + engine: &httputil.ReverseProxy{ + ErrorLog: log.New(os.Stdout, "", log.Ldate|log.Ltime|log.Lshortfile), + Director: func(req *http.Request) { + director(target, req) + }, + ModifyResponse: modifyResponse, + }, + } +} + +//ServeHTTP serves the incoming http requests +func (pe *ProxyEngine) ServeHTTP(w http.ResponseWriter, req *http.Request) { + pe.engine.ServeHTTP(w, req) +} + +//Overwrite the http requests +func director(target *url.URL, req *http.Request) { + targetQuery := target.RawQuery + + //Overwrite the request URL to the target path + req.URL.Scheme = target.Scheme + req.URL.Host = target.Host + req.URL.Path = singleJoiningSlash(target.Path, req.URL.Path) + if targetQuery == "" || req.URL.RawQuery == "" { + req.URL.RawQuery = targetQuery + req.URL.RawQuery + } else { + req.URL.RawQuery = targetQuery + "&" + req.URL.RawQuery + } + if _, ok := req.Header["User-Agent"]; !ok { + req.Header.Set("User-Agent", agentHarbor) + } + + //Get the password from the env + //Ignore the empty checking, the backend server should return the right status code + //with invalid credential + password := os.Getenv(passwordKey) + + //Add authentication header + req.SetBasicAuth(userName, password) +} + +//Modify the http response +func modifyResponse(res *http.Response) error { + //Detect the 401 code, if it is, + //overwrite it to 500. + //We also re-write the error content + if res.StatusCode == http.StatusUnauthorized { + errorObj := make(map[string]string) + errorObj["error"] = "operation request from unauthentic source is rejected" + content, err := json.Marshal(errorObj) + if err != nil { + return err + } + + size := len(content) + body := ioutil.NopCloser(bytes.NewReader(content)) + res.Body = body + res.ContentLength = int64(size) + res.Header.Set(contentLengthHeader, strconv.Itoa(size)) + res.StatusCode = http.StatusInternalServerError + } + + return nil +} + +//Join the path +//Copy from the go reverse proxy +func singleJoiningSlash(a, b string) string { + aslash := strings.HasSuffix(a, "/") + bslash := strings.HasPrefix(b, "/") + switch { + case aslash && bslash: + return a + b[1:] + case !aslash && !bslash: + return a + "/" + b + } + return a + b +}