move apigateway into apiserver (#1948)
This commit is contained in:
@@ -7,9 +7,18 @@ import (
|
||||
"github.com/emicklei/go-restful"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
urlruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
"k8s.io/apiserver/pkg/authentication/request/bearertoken"
|
||||
"k8s.io/apiserver/pkg/authorization/authorizerfactory"
|
||||
"k8s.io/apiserver/pkg/endpoints/handlers/responsewriters"
|
||||
"k8s.io/klog"
|
||||
"kubesphere.io/kubesphere/pkg/api/iam"
|
||||
"kubesphere.io/kubesphere/pkg/apiserver/authentication"
|
||||
"kubesphere.io/kubesphere/pkg/apiserver/dispatch"
|
||||
"kubesphere.io/kubesphere/pkg/apiserver/filters"
|
||||
"kubesphere.io/kubesphere/pkg/apiserver/request"
|
||||
"kubesphere.io/kubesphere/pkg/informers"
|
||||
devopsv1alpha2 "kubesphere.io/kubesphere/pkg/kapis/devops/v1alpha2"
|
||||
iamv1alpha2 "kubesphere.io/kubesphere/pkg/kapis/iam/v1alpha2"
|
||||
loggingv1alpha2 "kubesphere.io/kubesphere/pkg/kapis/logging/v1alpha2"
|
||||
monitoringv1alpha2 "kubesphere.io/kubesphere/pkg/kapis/monitoring/v1alpha2"
|
||||
@@ -18,14 +27,15 @@ import (
|
||||
resourcesv1alpha2 "kubesphere.io/kubesphere/pkg/kapis/resources/v1alpha2"
|
||||
resourcev1alpha3 "kubesphere.io/kubesphere/pkg/kapis/resources/v1alpha3"
|
||||
servicemeshv1alpha2 "kubesphere.io/kubesphere/pkg/kapis/servicemesh/metrics/v1alpha2"
|
||||
tenantv1alpha2 "kubesphere.io/kubesphere/pkg/kapis/tenant/v1alpha2"
|
||||
terminalv1alpha2 "kubesphere.io/kubesphere/pkg/kapis/terminal/v1alpha2"
|
||||
"kubesphere.io/kubesphere/pkg/simple/client/cache"
|
||||
"kubesphere.io/kubesphere/pkg/simple/client/db"
|
||||
"kubesphere.io/kubesphere/pkg/simple/client/devops"
|
||||
"kubesphere.io/kubesphere/pkg/simple/client/k8s"
|
||||
"kubesphere.io/kubesphere/pkg/simple/client/ldap"
|
||||
"kubesphere.io/kubesphere/pkg/simple/client/logging"
|
||||
"kubesphere.io/kubesphere/pkg/simple/client/monitoring"
|
||||
"kubesphere.io/kubesphere/pkg/simple/client/mysql"
|
||||
"kubesphere.io/kubesphere/pkg/simple/client/openpitrix"
|
||||
"kubesphere.io/kubesphere/pkg/simple/client/s3"
|
||||
"net"
|
||||
@@ -85,7 +95,7 @@ type APIServer struct {
|
||||
S3Client s3.Interface
|
||||
|
||||
//
|
||||
DBClient db.Interface
|
||||
DBClient *mysql.Client
|
||||
|
||||
//
|
||||
LdapClient ldap.Interface
|
||||
@@ -104,26 +114,31 @@ func (s *APIServer) PrepareRun() error {
|
||||
|
||||
s.installKubeSphereAPIs()
|
||||
|
||||
for _, ws := range s.container.RegisteredWebServices() {
|
||||
klog.V(2).Infof("%s", ws.RootPath())
|
||||
}
|
||||
|
||||
s.Server.Handler = s.container
|
||||
|
||||
s.buildHandlerChain()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *APIServer) installKubeSphereAPIs() {
|
||||
|
||||
urlruntime.Must(resourcev1alpha3.AddToContainer(s.container, s.InformerFactory))
|
||||
urlruntime.Must(servicemeshv1alpha2.AddToContainer(s.container))
|
||||
|
||||
// Need to refactor devops api registration, too much dependencies
|
||||
//urlruntime.Must(devopsv1alpha2.AddToContainer(s.container, s.DevopsClient))
|
||||
|
||||
urlruntime.Must(devopsv1alpha2.AddToContainer(s.container, s.DevopsClient, s.DBClient.Database(), nil, s.KubernetesClient.KubeSphere(), s.InformerFactory.KubeSphereSharedInformerFactory(), s.S3Client))
|
||||
urlruntime.Must(loggingv1alpha2.AddToContainer(s.container, s.KubernetesClient, s.LoggingClient))
|
||||
urlruntime.Must(monitoringv1alpha2.AddToContainer(s.container, s.KubernetesClient, s.MonitoringClient))
|
||||
urlruntime.Must(openpitrixv1.AddToContainer(s.container, s.InformerFactory, s.OpenpitrixClient))
|
||||
urlruntime.Must(operationsv1alpha2.AddToContainer(s.container, s.KubernetesClient.Kubernetes()))
|
||||
urlruntime.Must(resourcesv1alpha2.AddToContainer(s.container, s.KubernetesClient.Kubernetes(), s.InformerFactory))
|
||||
//urlruntime.Must(tenantv1alpha2.AddToContainer(s.container, s.KubernetesClient, s.InformerFactory, s.DBClient))
|
||||
urlruntime.Must(tenantv1alpha2.AddToContainer(s.container, s.KubernetesClient, s.InformerFactory, s.DBClient.Database()))
|
||||
urlruntime.Must(terminalv1alpha2.AddToContainer(s.container, s.KubernetesClient.Kubernetes(), s.KubernetesClient.Config()))
|
||||
urlruntime.Must(iamv1alpha2.AddToContainer(s.container, s.KubernetesClient, s.InformerFactory, s.LdapClient, s.CacheClient, s.AuthenticateOptions))
|
||||
|
||||
urlruntime.Must(servicemeshv1alpha2.AddToContainer(s.container))
|
||||
}
|
||||
|
||||
func (s *APIServer) Run(stopCh <-chan struct{}) error {
|
||||
@@ -141,6 +156,7 @@ func (s *APIServer) Run(stopCh <-chan struct{}) error {
|
||||
_ = s.Server.Shutdown(ctx)
|
||||
}()
|
||||
|
||||
klog.V(0).Infof("Start listening on %s", s.Server.Addr)
|
||||
if s.Server.TLSConfig != nil {
|
||||
return s.Server.ListenAndServeTLS("", "")
|
||||
} else {
|
||||
@@ -148,6 +164,27 @@ func (s *APIServer) Run(stopCh <-chan struct{}) error {
|
||||
}
|
||||
}
|
||||
|
||||
func (s *APIServer) buildHandlerChain() {
|
||||
requestInfoResolver := &request.RequestInfoFactory{
|
||||
APIPrefixes: sets.NewString("api", "apis", "kapis", "kapi"),
|
||||
GrouplessAPIPrefixes: sets.NewString("api", "kapi"),
|
||||
}
|
||||
|
||||
failed := http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
})
|
||||
|
||||
handler := s.Server.Handler
|
||||
handler = filters.WithKubeAPIServer(handler, s.KubernetesClient.Config(), &errorResponder{})
|
||||
handler = filters.WithMultipleClusterDispatcher(handler, dispatch.DefaultClusterDispatch)
|
||||
handler = filters.WithAuthorization(handler, authorizerfactory.NewAlwaysAllowAuthorizer())
|
||||
|
||||
handler = filters.WithAuthentication(handler, bearertoken.New(authentication.NewTokenAuthenticator(s.CacheClient)), failed)
|
||||
handler = filters.WithRequestInfo(handler, requestInfoResolver)
|
||||
|
||||
s.Server.Handler = handler
|
||||
}
|
||||
|
||||
func (s *APIServer) waitForResourceSync(stopCh <-chan struct{}) error {
|
||||
klog.V(0).Info("Start cache objects")
|
||||
|
||||
@@ -338,3 +375,10 @@ func getRequestIP(req *restful.Request) string {
|
||||
|
||||
return address
|
||||
}
|
||||
|
||||
type errorResponder struct{}
|
||||
|
||||
func (e *errorResponder) Error(w http.ResponseWriter, req *http.Request, err error) {
|
||||
klog.Error(err)
|
||||
responsewriters.InternalError(w, req, err)
|
||||
}
|
||||
|
||||
29
pkg/apiserver/authentication/token_authenticator.go
Normal file
29
pkg/apiserver/authentication/token_authenticator.go
Normal file
@@ -0,0 +1,29 @@
|
||||
package authentication
|
||||
|
||||
import (
|
||||
"context"
|
||||
"k8s.io/apiserver/pkg/authentication/authenticator"
|
||||
"k8s.io/apiserver/pkg/authentication/user"
|
||||
"kubesphere.io/kubesphere/pkg/simple/client/cache"
|
||||
)
|
||||
|
||||
type TokenAuthenticator struct {
|
||||
cacheClient cache.Interface
|
||||
}
|
||||
|
||||
func NewTokenAuthenticator(cacheClient cache.Interface) authenticator.Token {
|
||||
return &TokenAuthenticator{
|
||||
cacheClient: cacheClient,
|
||||
}
|
||||
}
|
||||
|
||||
func (t *TokenAuthenticator) AuthenticateToken(ctx context.Context, token string) (*authenticator.Response, bool, error) {
|
||||
return &authenticator.Response{
|
||||
User: &user.DefaultInfo{
|
||||
Name: "admin",
|
||||
UID: "",
|
||||
Groups: nil,
|
||||
Extra: nil,
|
||||
},
|
||||
}, true, nil
|
||||
}
|
||||
36
pkg/apiserver/dispatch/dispatch.go
Normal file
36
pkg/apiserver/dispatch/dispatch.go
Normal file
@@ -0,0 +1,36 @@
|
||||
package dispatch
|
||||
|
||||
import (
|
||||
"k8s.io/apiserver/pkg/endpoints/handlers/responsewriters"
|
||||
"net/http"
|
||||
|
||||
"k8s.io/apimachinery/pkg/util/proxy"
|
||||
)
|
||||
|
||||
// Dispatcher defines how to forward request to desired cluster apiserver
|
||||
type Dispatcher interface {
|
||||
Dispatch(w http.ResponseWriter, req *http.Request)
|
||||
}
|
||||
|
||||
var DefaultClusterDispatch = newClusterDispatch()
|
||||
|
||||
type clusterDispatch struct {
|
||||
transport *http.Transport
|
||||
}
|
||||
|
||||
func newClusterDispatch() Dispatcher {
|
||||
return &clusterDispatch{}
|
||||
}
|
||||
|
||||
func (c *clusterDispatch) Dispatch(w http.ResponseWriter, req *http.Request) {
|
||||
|
||||
u := *req.URL
|
||||
// u.Host = someHost
|
||||
|
||||
httpProxy := proxy.NewUpgradeAwareHandler(&u, c.transport, false, false, c)
|
||||
httpProxy.ServeHTTP(w, req)
|
||||
}
|
||||
|
||||
func (c *clusterDispatch) Error(w http.ResponseWriter, req *http.Request, err error) {
|
||||
responsewriters.InternalError(w, req, err)
|
||||
}
|
||||
33
pkg/apiserver/filters/authentication.go
Normal file
33
pkg/apiserver/filters/authentication.go
Normal file
@@ -0,0 +1,33 @@
|
||||
package filters
|
||||
|
||||
import (
|
||||
"k8s.io/apiserver/pkg/authentication/authenticator"
|
||||
"k8s.io/apiserver/pkg/endpoints/request"
|
||||
"k8s.io/klog"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func WithAuthentication(handler http.Handler, auth authenticator.Request, failed http.Handler) http.Handler {
|
||||
if auth == nil {
|
||||
klog.Warningf("Authentication is disabled")
|
||||
return handler
|
||||
}
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
//authenticationStart := time.Now()
|
||||
|
||||
resp, ok, err := auth.AuthenticateRequest(req)
|
||||
if err != nil || !ok {
|
||||
if err != nil {
|
||||
klog.Errorf("Unable to authenticate the request due to error: %v", err)
|
||||
}
|
||||
failed.ServeHTTP(w, req)
|
||||
return
|
||||
}
|
||||
|
||||
// authorization header is not required anymore in case of a successful authentication.
|
||||
req.Header.Del("Authorization")
|
||||
|
||||
req = req.WithContext(request.WithUser(req.Context(), resp.User))
|
||||
handler.ServeHTTP(w, req)
|
||||
})
|
||||
}
|
||||
71
pkg/apiserver/filters/authorization.go
Normal file
71
pkg/apiserver/filters/authorization.go
Normal file
@@ -0,0 +1,71 @@
|
||||
package filters
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"k8s.io/apiserver/pkg/authorization/authorizer"
|
||||
"k8s.io/apiserver/pkg/endpoints/handlers/responsewriters"
|
||||
k8srequest "k8s.io/apiserver/pkg/endpoints/request"
|
||||
"k8s.io/klog"
|
||||
"kubesphere.io/kubesphere/pkg/apiserver/request"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// WithAuthorization passes all authorized requests on to handler, and returns forbidden error otherwise.
|
||||
func WithAuthorization(handler http.Handler, a authorizer.Authorizer) http.Handler {
|
||||
if a == nil {
|
||||
klog.Warningf("Authorization is disabled")
|
||||
return handler
|
||||
}
|
||||
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
ctx := req.Context()
|
||||
|
||||
attributes, err := GetAuthorizerAttributes(ctx)
|
||||
if err != nil {
|
||||
responsewriters.InternalError(w, req, err)
|
||||
}
|
||||
|
||||
authorized, reason, err := a.Authorize(attributes)
|
||||
if authorized == authorizer.DecisionAllow {
|
||||
handler.ServeHTTP(w, req)
|
||||
return
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
responsewriters.InternalError(w, req, err)
|
||||
return
|
||||
}
|
||||
|
||||
klog.V(4).Infof("Forbidden: %#v, Reason: %q", req.RequestURI, reason)
|
||||
w.WriteHeader(http.StatusForbidden)
|
||||
})
|
||||
}
|
||||
|
||||
func GetAuthorizerAttributes(ctx context.Context) (authorizer.Attributes, error) {
|
||||
attribs := authorizer.AttributesRecord{}
|
||||
|
||||
user, ok := k8srequest.UserFrom(ctx)
|
||||
if ok {
|
||||
attribs.User = user
|
||||
}
|
||||
|
||||
requestInfo, found := request.RequestInfoFrom(ctx)
|
||||
if !found {
|
||||
return nil, errors.New("no RequestInfo found in the context")
|
||||
}
|
||||
|
||||
// Start with common attributes that apply to resource and non-resource requests
|
||||
attribs.ResourceRequest = requestInfo.IsResourceRequest
|
||||
attribs.Path = requestInfo.Path
|
||||
attribs.Verb = requestInfo.Verb
|
||||
|
||||
attribs.APIGroup = requestInfo.APIGroup
|
||||
attribs.APIVersion = requestInfo.APIVersion
|
||||
attribs.Resource = requestInfo.Resource
|
||||
attribs.Subresource = requestInfo.Subresource
|
||||
attribs.Namespace = requestInfo.Namespace
|
||||
attribs.Name = requestInfo.Name
|
||||
|
||||
return &attribs, nil
|
||||
}
|
||||
32
pkg/apiserver/filters/dispatch.go
Normal file
32
pkg/apiserver/filters/dispatch.go
Normal file
@@ -0,0 +1,32 @@
|
||||
package filters
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"k8s.io/apiserver/pkg/endpoints/handlers/responsewriters"
|
||||
"k8s.io/klog"
|
||||
"kubesphere.io/kubesphere/pkg/apiserver/dispatch"
|
||||
"kubesphere.io/kubesphere/pkg/apiserver/request"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// Multiple cluster dispatcher forward request to desired cluster based on request cluster name
|
||||
// which included in request path clusters/{cluster}
|
||||
func WithMultipleClusterDispatcher(handler http.Handler, dispatch dispatch.Dispatcher) http.Handler {
|
||||
if dispatch == nil {
|
||||
klog.V(4).Infof("Multiple cluster dispatcher is disabled")
|
||||
return handler
|
||||
}
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
info, ok := request.RequestInfoFrom(req.Context())
|
||||
if !ok {
|
||||
responsewriters.InternalError(w, req, fmt.Errorf(""))
|
||||
return
|
||||
}
|
||||
|
||||
if info.Cluster == "" {
|
||||
handler.ServeHTTP(w, req)
|
||||
} else {
|
||||
dispatch.Dispatch(w, req)
|
||||
}
|
||||
})
|
||||
}
|
||||
44
pkg/apiserver/filters/kubeapiserver.go
Normal file
44
pkg/apiserver/filters/kubeapiserver.go
Normal file
@@ -0,0 +1,44 @@
|
||||
package filters
|
||||
|
||||
import (
|
||||
"k8s.io/apiserver/pkg/endpoints/handlers/responsewriters"
|
||||
"k8s.io/client-go/rest"
|
||||
"k8s.io/klog"
|
||||
"kubesphere.io/kubesphere/pkg/apiserver/request"
|
||||
"kubesphere.io/kubesphere/pkg/server/errors"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"k8s.io/apimachinery/pkg/util/proxy"
|
||||
)
|
||||
|
||||
// WithKubeAPIServer proxy request to kubernetes service if requests path starts with /api
|
||||
func WithKubeAPIServer(handler http.Handler, config *rest.Config, failed proxy.ErrorResponder) http.Handler {
|
||||
kubernetes, _ := url.Parse(config.Host)
|
||||
defaultTransport, err := rest.TransportFor(config)
|
||||
if err != nil {
|
||||
klog.Errorf("Unable to create transport from rest.Config: %v", err)
|
||||
return handler
|
||||
}
|
||||
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
info, ok := request.RequestInfoFrom(req.Context())
|
||||
if !ok {
|
||||
err := errors.New("Unable to retrieve request info from request")
|
||||
klog.Error(err)
|
||||
responsewriters.InternalError(w, req, err)
|
||||
}
|
||||
|
||||
if info.IsKubernetesRequest {
|
||||
s := *req.URL
|
||||
s.Host = kubernetes.Host
|
||||
s.Scheme = kubernetes.Scheme
|
||||
|
||||
httpProxy := proxy.NewUpgradeAwareHandler(&s, defaultTransport, true, false, failed)
|
||||
httpProxy.ServeHTTP(w, req)
|
||||
return
|
||||
}
|
||||
|
||||
handler.ServeHTTP(w, req)
|
||||
})
|
||||
}
|
||||
22
pkg/apiserver/filters/requestinfo.go
Normal file
22
pkg/apiserver/filters/requestinfo.go
Normal file
@@ -0,0 +1,22 @@
|
||||
package filters
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"k8s.io/apiserver/pkg/endpoints/handlers/responsewriters"
|
||||
"kubesphere.io/kubesphere/pkg/apiserver/request"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func WithRequestInfo(handler http.Handler, resolver request.RequestInfoResolver) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
ctx := req.Context()
|
||||
info, err := resolver.NewRequestInfo(req)
|
||||
if err != nil {
|
||||
responsewriters.InternalError(w, req, fmt.Errorf("failed to crate RequestInfo: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
req = req.WithContext(request.WithRequestInfo(ctx, info))
|
||||
handler.ServeHTTP(w, req)
|
||||
})
|
||||
}
|
||||
173
pkg/apiserver/request/requestinfo.go
Normal file
173
pkg/apiserver/request/requestinfo.go
Normal file
@@ -0,0 +1,173 @@
|
||||
package request
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
k8srequest "k8s.io/apiserver/pkg/endpoints/request"
|
||||
)
|
||||
|
||||
type RequestInfoResolver interface {
|
||||
NewRequestInfo(req *http.Request) (*RequestInfo, error)
|
||||
}
|
||||
|
||||
// specialVerbs contains just strings which are used in REST paths for special actions that don't fall under the normal
|
||||
// CRUDdy GET/POST/PUT/DELETE actions on REST objects.
|
||||
// master's Mux.
|
||||
var specialVerbs = sets.NewString("proxy", "watch")
|
||||
|
||||
var kubernetesAPIPrefixes = sets.NewString("api", "apis")
|
||||
|
||||
// RequestInfo holds information parsed from the http.Request,
|
||||
// extended from k8s.io/apiserver/pkg/endpoints/request/requestinfo.go
|
||||
type RequestInfo struct {
|
||||
*k8srequest.RequestInfo
|
||||
|
||||
// IsKubeSphereRequest indicates whether or not the request should be handled by kubernetes or kubesphere
|
||||
IsKubernetesRequest bool
|
||||
|
||||
// Workspace of requested namespace, for non-workspaced resources, this may be empty
|
||||
Workspace string
|
||||
|
||||
// Cluster of requested resource, this is empty in single-cluster environment
|
||||
Cluster string
|
||||
}
|
||||
|
||||
type RequestInfoFactory struct {
|
||||
APIPrefixes sets.String
|
||||
GrouplessAPIPrefixes sets.String
|
||||
k8sRequestInfoFactory *k8srequest.RequestInfoFactory
|
||||
}
|
||||
|
||||
// NewRequestInfo returns the information from the http request. If error is not nil, RequestInfo holds the information as best it is known before the failure
|
||||
// It handles both resource and non-resource requests and fills in all the pertinent information for each.
|
||||
// Valid Inputs:
|
||||
//
|
||||
// /apis/{api-group}/{version}/namespaces
|
||||
// /api/{version}/namespaces
|
||||
// /api/{version}/namespaces/{namespace}
|
||||
// /api/{version}/namespaces/{namespace}/{resource}
|
||||
// /api/{version}/namespaces/{namespace}/{resource}/{resourceName}
|
||||
// /api/{version}/{resource}
|
||||
// /api/{version}/{resource}/{resourceName}
|
||||
//
|
||||
// Special verbs without subresources:
|
||||
// /api/{version}/proxy/{resource}/{resourceName}
|
||||
// /api/{version}/proxy/namespaces/{namespace}/{resource}/{resourceName}
|
||||
//
|
||||
// Special verbs with subresources:
|
||||
// /api/{version}/watch/{resource}
|
||||
// /api/{version}/watch/namespaces/{namespace}/{resource}
|
||||
//
|
||||
// /kapis/{api-group}/{version}/workspaces/{workspace}/{resource}/{resourceName}
|
||||
// /
|
||||
// /kapis/{api-group}/{version}/namespaces/{namespace}/{resource}
|
||||
// /kapis/{api-group}/{version}/namespaces/{namespace}/{resource}/{resourceName}
|
||||
// With workspaces:
|
||||
// /kapis/{api-group}/{version}/clusters/{cluster}/namespaces/{namespace}/{resource}
|
||||
// /kapis/{api-group}/{version}/clusters/{cluster}/namespaces/{namespace}/{resource}/{resourceName}
|
||||
//
|
||||
func (r *RequestInfoFactory) NewRequestInfo(req *http.Request) (*RequestInfo, error) {
|
||||
|
||||
requestInfo := RequestInfo{
|
||||
IsKubernetesRequest: false,
|
||||
RequestInfo: &k8srequest.RequestInfo{
|
||||
Path: req.URL.Path,
|
||||
Verb: req.Method,
|
||||
},
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if kubernetesAPIPrefixes.Has(requestInfo.APIPrefix) {
|
||||
requestInfo.IsKubernetesRequest = true
|
||||
}
|
||||
}()
|
||||
|
||||
currentParts := splitPath(req.URL.Path)
|
||||
if len(currentParts) < 3 {
|
||||
return &requestInfo, nil
|
||||
}
|
||||
|
||||
if !r.APIPrefixes.Has(currentParts[0]) {
|
||||
// return a non-resource request
|
||||
return &requestInfo, nil
|
||||
}
|
||||
requestInfo.APIPrefix = currentParts[0]
|
||||
currentParts = currentParts[1:]
|
||||
|
||||
if !r.GrouplessAPIPrefixes.Has(requestInfo.APIPrefix) {
|
||||
if len(currentParts) < 2 {
|
||||
return &requestInfo, nil
|
||||
}
|
||||
|
||||
if currentParts[0] == "clusters" {
|
||||
requestInfo.Cluster = currentParts[1]
|
||||
currentParts = currentParts[2:]
|
||||
}
|
||||
|
||||
if len(currentParts) < 3 {
|
||||
return &requestInfo, nil
|
||||
}
|
||||
|
||||
requestInfo.APIGroup = currentParts[0]
|
||||
currentParts = currentParts[1:]
|
||||
}
|
||||
|
||||
requestInfo.IsResourceRequest = true
|
||||
requestInfo.APIVersion = currentParts[0]
|
||||
currentParts = currentParts[1:]
|
||||
|
||||
if specialVerbs.Has(currentParts[0]) {
|
||||
if len(currentParts) < 2 {
|
||||
return &requestInfo, fmt.Errorf("unable to determine kind and namespace from url: %v", req.URL)
|
||||
}
|
||||
|
||||
requestInfo.Verb = currentParts[0]
|
||||
currentParts = currentParts[1:]
|
||||
} else {
|
||||
switch req.Method {
|
||||
case "POST":
|
||||
requestInfo.Verb = "create"
|
||||
case "GET", "HEAD":
|
||||
requestInfo.Verb = "get"
|
||||
case "PUT":
|
||||
requestInfo.Verb = "update"
|
||||
case "PATCH":
|
||||
requestInfo.Verb = "patch"
|
||||
case "DELETE":
|
||||
requestInfo.Verb = "delete"
|
||||
default:
|
||||
requestInfo.Verb = ""
|
||||
}
|
||||
}
|
||||
|
||||
return &requestInfo, nil
|
||||
}
|
||||
|
||||
type requestInfoKeyType int
|
||||
|
||||
// requestInfoKey is the RequestInfo key for the context. It's of private type here. Because
|
||||
// keys are interfaces and interfaces are equal when the type and the value is equal, this
|
||||
// does not conflict with the keys defined in pkg/api.
|
||||
const requestInfoKey requestInfoKeyType = iota
|
||||
|
||||
func WithRequestInfo(parent context.Context, info *RequestInfo) context.Context {
|
||||
return k8srequest.WithValue(parent, requestInfoKey, info)
|
||||
}
|
||||
|
||||
func RequestInfoFrom(ctx context.Context) (*RequestInfo, bool) {
|
||||
info, ok := ctx.Value(requestInfoKey).(*RequestInfo)
|
||||
return info, ok
|
||||
}
|
||||
|
||||
// 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, "/")
|
||||
}
|
||||
1
pkg/apiserver/server/handler.go
Normal file
1
pkg/apiserver/server/handler.go
Normal file
@@ -0,0 +1 @@
|
||||
package server
|
||||
@@ -1,70 +0,0 @@
|
||||
package metrics
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/emicklei/go-restful"
|
||||
"github.com/kiali/kiali/handlers"
|
||||
)
|
||||
|
||||
// Get app metrics
|
||||
func GetAppMetrics(request *restful.Request, response *restful.Response) {
|
||||
handlers.AppMetrics(request, response)
|
||||
}
|
||||
|
||||
// Get workload metrics
|
||||
func GetWorkloadMetrics(request *restful.Request, response *restful.Response) {
|
||||
namespace := request.PathParameter("namespace")
|
||||
workload := request.PathParameter("workload")
|
||||
|
||||
if len(namespace) > 0 && len(workload) > 0 {
|
||||
request.Request.URL.RawQuery = fmt.Sprintf("%s&namespaces=%s&workload=%s", request.Request.URL.RawQuery, namespace, workload)
|
||||
}
|
||||
|
||||
handlers.WorkloadMetrics(request, response)
|
||||
}
|
||||
|
||||
// Get service metrics
|
||||
func GetServiceMetrics(request *restful.Request, response *restful.Response) {
|
||||
handlers.ServiceMetrics(request, response)
|
||||
}
|
||||
|
||||
// Get namespace metrics
|
||||
func GetNamespaceMetrics(request *restful.Request, response *restful.Response) {
|
||||
handlers.NamespaceMetrics(request, response)
|
||||
}
|
||||
|
||||
// Get service graph for namespace
|
||||
func GetNamespaceGraph(request *restful.Request, response *restful.Response) {
|
||||
namespace := request.PathParameter("namespace")
|
||||
|
||||
if len(namespace) > 0 {
|
||||
request.Request.URL.RawQuery = fmt.Sprintf("%s&namespaces=%s", request.Request.URL.RawQuery, namespace)
|
||||
}
|
||||
|
||||
handlers.GetNamespaceGraph(request, response)
|
||||
}
|
||||
|
||||
// Get service graph for namespaces
|
||||
func GetNamespacesGraph(request *restful.Request, response *restful.Response) {
|
||||
handlers.GraphNamespaces(request, response)
|
||||
}
|
||||
|
||||
// Get namespace health
|
||||
func GetNamespaceHealth(request *restful.Request, response *restful.Response) {
|
||||
handlers.NamespaceHealth(request, response)
|
||||
}
|
||||
|
||||
// Get workload health
|
||||
func GetWorkloadHealth(request *restful.Request, response *restful.Response) {
|
||||
handlers.WorkloadHealth(request, response)
|
||||
}
|
||||
|
||||
// Get app health
|
||||
func GetAppHealth(request *restful.Request, response *restful.Response) {
|
||||
handlers.AppHealth(request, response)
|
||||
}
|
||||
|
||||
// Get service health
|
||||
func GetServiceHealth(request *restful.Request, response *restful.Response) {
|
||||
handlers.ServiceHealth(request, response)
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
package tracing
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/emicklei/go-restful"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
var JaegerQueryUrl = "http://jaeger-query.istio-system.svc:16686/jaeger"
|
||||
|
||||
func GetServiceTracing(request *restful.Request, response *restful.Response) {
|
||||
namespace := request.PathParameter("namespace")
|
||||
service := request.PathParameter("service")
|
||||
|
||||
serviceName := fmt.Sprintf("%s.%s", service, namespace)
|
||||
|
||||
url := fmt.Sprintf("%s/api/traces?%s&service=%s", JaegerQueryUrl, request.Request.URL.RawQuery, serviceName)
|
||||
|
||||
resp, err := http.Get(url)
|
||||
|
||||
if err != nil {
|
||||
log.Printf("query jaeger faile with err %v", err)
|
||||
response.WriteError(http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
defer resp.Body.Close()
|
||||
|
||||
if err != nil {
|
||||
log.Printf("read response error : %v", err)
|
||||
response.WriteError(http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
|
||||
// need to set header for proper response
|
||||
response.Header().Set("Content-Type", "application/json")
|
||||
_, err = response.Write(body)
|
||||
|
||||
if err != nil {
|
||||
log.Printf("write response failed %v", err)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user