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
This commit is contained in:
Steven Zou 2018-07-03 21:36:20 +08:00
parent 54a0d10994
commit ce4e5ac01d
5 changed files with 150 additions and 18 deletions

View File

@ -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)
}

View File

@ -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{}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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
}