Merge remote-tracking branch 'upstream/dev' into dev
# Conflicts: # pkg/apis/iam/v1alpha2/user_types.go
This commit is contained in:
@@ -185,7 +185,9 @@ func (s *APIServer) buildHandlerChain() {
|
||||
|
||||
handler := s.Server.Handler
|
||||
handler = filters.WithKubeAPIServer(handler, s.KubernetesClient.Config(), &errorResponder{})
|
||||
handler = filters.WithMultipleClusterDispatcher(handler, dispatch.NewClusterDispatch(s.InformerFactory.KubeSphereSharedInformerFactory().Tower().V1alpha1().Agents().Lister()))
|
||||
|
||||
clusterDispatcher := dispatch.NewClusterDispatch(s.InformerFactory.KubeSphereSharedInformerFactory().Cluster().V1alpha1().Agents().Lister(), s.InformerFactory.KubeSphereSharedInformerFactory().Cluster().V1alpha1().Clusters().Lister())
|
||||
handler = filters.WithMultipleClusterDispatcher(handler, clusterDispatcher)
|
||||
|
||||
excludedPaths := []string{"/oauth/*", "/kapis/config.kubesphere.io/*"}
|
||||
pathAuthorizer, _ := path.NewAuthorizer(excludedPaths)
|
||||
|
||||
@@ -2,11 +2,8 @@ package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/emicklei/go-restful"
|
||||
"github.com/spf13/viper"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
authoptions "kubesphere.io/kubesphere/pkg/apiserver/authentication/options"
|
||||
"kubesphere.io/kubesphere/pkg/apiserver/runtime"
|
||||
"kubesphere.io/kubesphere/pkg/simple/client/alerting"
|
||||
"kubesphere.io/kubesphere/pkg/simple/client/cache"
|
||||
"kubesphere.io/kubesphere/pkg/simple/client/devops/jenkins"
|
||||
@@ -20,7 +17,6 @@ import (
|
||||
"kubesphere.io/kubesphere/pkg/simple/client/s3"
|
||||
"kubesphere.io/kubesphere/pkg/simple/client/servicemesh"
|
||||
"kubesphere.io/kubesphere/pkg/simple/client/sonarqube"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
@@ -126,26 +122,6 @@ func TryLoadFromDisk() (*Config, error) {
|
||||
return conf, nil
|
||||
}
|
||||
|
||||
// InstallAPI installs api for config
|
||||
func (conf *Config) InstallAPI(c *restful.Container) {
|
||||
ws := runtime.NewWebService(schema.GroupVersion{
|
||||
Group: "",
|
||||
Version: "v1alpha1",
|
||||
})
|
||||
|
||||
ws.Route(ws.GET("/configz").
|
||||
To(func(request *restful.Request, response *restful.Response) {
|
||||
conf.stripEmptyOptions()
|
||||
response.WriteAsJson(conf.ToMap())
|
||||
}).
|
||||
Doc("Get system components configuration").
|
||||
Produces(restful.MIME_JSON).
|
||||
Writes(Config{}).
|
||||
Returns(http.StatusOK, "ok", Config{}))
|
||||
|
||||
c.Add(ws)
|
||||
}
|
||||
|
||||
// convertToMap simply converts config to map[string]bool
|
||||
// to hide sensitive information
|
||||
func (conf *Config) ToMap() map[string]bool {
|
||||
|
||||
@@ -6,39 +6,59 @@ import (
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/util/proxy"
|
||||
"k8s.io/apiserver/pkg/endpoints/handlers/responsewriters"
|
||||
towerv1alpha1 "kubesphere.io/kubesphere/pkg/apis/tower/v1alpha1"
|
||||
"k8s.io/klog"
|
||||
clusterv1alpha1 "kubesphere.io/kubesphere/pkg/apis/cluster/v1alpha1"
|
||||
"kubesphere.io/kubesphere/pkg/apiserver/request"
|
||||
"kubesphere.io/kubesphere/pkg/client/listers/tower/v1alpha1"
|
||||
"kubesphere.io/kubesphere/pkg/client/listers/cluster/v1alpha1"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const defaultMultipleClusterAgentNamespace = "kubesphere-system"
|
||||
|
||||
// Dispatcher defines how to forward request to designated cluster based on cluster name
|
||||
type Dispatcher interface {
|
||||
Dispatch(w http.ResponseWriter, req *http.Request, handler http.Handler)
|
||||
}
|
||||
|
||||
type clusterDispatch struct {
|
||||
agentLister v1alpha1.AgentLister
|
||||
agentLister v1alpha1.AgentLister
|
||||
clusterLister v1alpha1.ClusterLister
|
||||
}
|
||||
|
||||
func NewClusterDispatch(agentLister v1alpha1.AgentLister) Dispatcher {
|
||||
func NewClusterDispatch(agentLister v1alpha1.AgentLister, clusterLister v1alpha1.ClusterLister) Dispatcher {
|
||||
return &clusterDispatch{
|
||||
agentLister: agentLister,
|
||||
agentLister: agentLister,
|
||||
clusterLister: clusterLister,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *clusterDispatch) Dispatch(w http.ResponseWriter, req *http.Request, handler http.Handler) {
|
||||
|
||||
info, _ := request.RequestInfoFrom(req.Context())
|
||||
if info.Cluster == "" { // fallback to host cluster if cluster name if empty
|
||||
|
||||
if len(info.Cluster) == 0 {
|
||||
klog.Warningf("Request with empty cluster, %v", req.URL)
|
||||
http.Error(w, fmt.Sprintf("Bad request, empty cluster"), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
cluster, err := c.clusterLister.Get(info.Cluster)
|
||||
if err != nil {
|
||||
if errors.IsNotFound(err) {
|
||||
http.Error(w, fmt.Sprintf("cluster %s not found", info.Cluster), http.StatusNotFound)
|
||||
} else {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// request cluster is host cluster, no need go through agent
|
||||
if isClusterHostCluster(cluster) {
|
||||
req.URL.Path = strings.Replace(req.URL.Path, fmt.Sprintf("/clusters/%s", info.Cluster), "", 1)
|
||||
handler.ServeHTTP(w, req)
|
||||
return
|
||||
}
|
||||
|
||||
agent, err := c.agentLister.Agents(defaultMultipleClusterAgentNamespace).Get(info.Cluster)
|
||||
agent, err := c.agentLister.Get(info.Cluster)
|
||||
if err != nil {
|
||||
if errors.IsNotFound(err) {
|
||||
http.Error(w, fmt.Sprintf("cluster %s not found", info.Cluster), http.StatusNotFound)
|
||||
@@ -65,9 +85,20 @@ func (c *clusterDispatch) Error(w http.ResponseWriter, req *http.Request, err er
|
||||
responsewriters.InternalError(w, req, err)
|
||||
}
|
||||
|
||||
func isAgentReady(agent *towerv1alpha1.Agent) bool {
|
||||
func isAgentReady(agent *clusterv1alpha1.Agent) bool {
|
||||
for _, condition := range agent.Status.Conditions {
|
||||
if condition.Type == towerv1alpha1.AgentConnected && condition.Status == corev1.ConditionTrue {
|
||||
if condition.Type == clusterv1alpha1.AgentConnected && condition.Status == corev1.ConditionTrue {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
//
|
||||
func isClusterHostCluster(cluster *clusterv1alpha1.Cluster) bool {
|
||||
for key, value := range cluster.Annotations {
|
||||
if key == clusterv1alpha1.IsHostCluster && value == "true" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package filters
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"k8s.io/apimachinery/pkg/util/proxy"
|
||||
"k8s.io/apiserver/pkg/endpoints/handlers/responsewriters"
|
||||
"k8s.io/client-go/rest"
|
||||
"k8s.io/klog"
|
||||
@@ -9,9 +9,6 @@ import (
|
||||
"kubesphere.io/kubesphere/pkg/server/errors"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"k8s.io/apimachinery/pkg/util/proxy"
|
||||
)
|
||||
|
||||
// WithKubeAPIServer proxy request to kubernetes service if requests path starts with /api
|
||||
@@ -35,8 +32,6 @@ func WithKubeAPIServer(handler http.Handler, config *rest.Config, failed proxy.E
|
||||
s := *req.URL
|
||||
s.Host = kubernetes.Host
|
||||
s.Scheme = kubernetes.Scheme
|
||||
// remove cluster path
|
||||
s.Path = strings.Replace(s.Path, fmt.Sprintf("/clusters/%s", info.Cluster), "", 1)
|
||||
|
||||
httpProxy := proxy.NewUpgradeAwareHandler(&s, defaultTransport, true, false, failed)
|
||||
httpProxy.ServeHTTP(w, req)
|
||||
|
||||
@@ -78,8 +78,8 @@ type RequestInfoFactory struct {
|
||||
// /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}
|
||||
// /kapis/clusters/{cluster}/{api-group}/{version}/namespaces/{namespace}/{resource}
|
||||
// /kapis/clusters/{cluster}/{api-group}/{version}/namespaces/{namespace}/{resource}/{resourceName}
|
||||
//
|
||||
func (r *RequestInfoFactory) NewRequestInfo(req *http.Request) (*RequestInfo, error) {
|
||||
|
||||
@@ -111,6 +111,16 @@ func (r *RequestInfoFactory) NewRequestInfo(req *http.Request) (*RequestInfo, er
|
||||
requestInfo.APIPrefix = currentParts[0]
|
||||
currentParts = currentParts[1:]
|
||||
|
||||
// URL forms: /clusters/{cluster}/*
|
||||
if currentParts[0] == "clusters" {
|
||||
if len(currentParts) > 1 {
|
||||
requestInfo.Cluster = currentParts[1]
|
||||
}
|
||||
if len(currentParts) > 2 {
|
||||
currentParts = currentParts[2:]
|
||||
}
|
||||
}
|
||||
|
||||
if !r.GrouplessAPIPrefixes.Has(requestInfo.APIPrefix) {
|
||||
// one part (APIPrefix) has already been consumed, so this is actually "do we have four parts?"
|
||||
if len(currentParts) < 3 {
|
||||
@@ -150,16 +160,6 @@ func (r *RequestInfoFactory) NewRequestInfo(req *http.Request) (*RequestInfo, er
|
||||
}
|
||||
}
|
||||
|
||||
// URL forms: /clusters/{cluster}/*
|
||||
if currentParts[0] == "clusters" {
|
||||
if len(currentParts) > 1 {
|
||||
requestInfo.Cluster = currentParts[1]
|
||||
}
|
||||
if len(currentParts) > 2 {
|
||||
currentParts = currentParts[2:]
|
||||
}
|
||||
}
|
||||
|
||||
// URL forms: /workspaces/{workspace}/*
|
||||
if currentParts[0] == "workspaces" {
|
||||
if len(currentParts) > 1 {
|
||||
|
||||
@@ -59,40 +59,18 @@ func TestRequestInfoFactory_NewRequestInfo(t *testing.T) {
|
||||
expectedKubernetesRequest: false,
|
||||
},
|
||||
{
|
||||
name: "list cluster roles",
|
||||
url: "/apis/rbac.authorization.k8s.io/v1/clusters/cluster1/clusterroles",
|
||||
name: "list clusterRoles of cluster gondor",
|
||||
url: "/apis/clusters/gondor/rbac.authorization.k8s.io/v1/clusterroles",
|
||||
method: http.MethodGet,
|
||||
expectedErr: nil,
|
||||
expectedVerb: "list",
|
||||
expectedResource: "clusterroles",
|
||||
expectedIsResourceRequest: true,
|
||||
expectedCluster: "cluster1",
|
||||
expectedCluster: "gondor",
|
||||
expectedKubernetesRequest: true,
|
||||
},
|
||||
{
|
||||
name: "list cluster nodes",
|
||||
url: "/api/v1/clusters/cluster1/nodes",
|
||||
method: http.MethodGet,
|
||||
expectedErr: nil,
|
||||
expectedVerb: "list",
|
||||
expectedResource: "nodes",
|
||||
expectedIsResourceRequest: true,
|
||||
expectedCluster: "cluster1",
|
||||
expectedKubernetesRequest: true,
|
||||
},
|
||||
{
|
||||
name: "list cluster nodes",
|
||||
url: "/api/v1/clusters/cluster1/nodes",
|
||||
method: http.MethodGet,
|
||||
expectedErr: nil,
|
||||
expectedVerb: "list",
|
||||
expectedResource: "nodes",
|
||||
expectedIsResourceRequest: true,
|
||||
expectedCluster: "cluster1",
|
||||
expectedKubernetesRequest: true,
|
||||
},
|
||||
{
|
||||
name: "list cluster nodes",
|
||||
name: "list nodes",
|
||||
url: "/api/v1/nodes",
|
||||
method: http.MethodGet,
|
||||
expectedErr: nil,
|
||||
@@ -103,15 +81,26 @@ func TestRequestInfoFactory_NewRequestInfo(t *testing.T) {
|
||||
expectedKubernetesRequest: true,
|
||||
},
|
||||
{
|
||||
name: "list roles",
|
||||
url: "/apis/rbac.authorization.k8s.io/v1/clusters/cluster1/namespaces/namespace1/roles",
|
||||
name: "list nodes of cluster gondor",
|
||||
url: "/api/clusters/gondor/v1/nodes",
|
||||
method: http.MethodGet,
|
||||
expectedErr: nil,
|
||||
expectedVerb: "list",
|
||||
expectedResource: "nodes",
|
||||
expectedIsResourceRequest: true,
|
||||
expectedCluster: "gondor",
|
||||
expectedKubernetesRequest: true,
|
||||
},
|
||||
{
|
||||
name: "list roles of cluster gondor",
|
||||
url: "/apis/clusters/gondor/rbac.authorization.k8s.io/v1/namespaces/namespace1/roles",
|
||||
method: http.MethodGet,
|
||||
expectedErr: nil,
|
||||
expectedVerb: "list",
|
||||
expectedResource: "roles",
|
||||
expectedIsResourceRequest: true,
|
||||
expectedNamespace: "namespace1",
|
||||
expectedCluster: "cluster1",
|
||||
expectedCluster: "gondor",
|
||||
expectedKubernetesRequest: true,
|
||||
},
|
||||
{
|
||||
@@ -139,17 +128,41 @@ func TestRequestInfoFactory_NewRequestInfo(t *testing.T) {
|
||||
expectedKubernetesRequest: false,
|
||||
},
|
||||
{
|
||||
name: "list namespaces",
|
||||
url: "/kapis/resources.kubesphere.io/v1alpha3/clusters/cluster1/workspaces/workspace1/namespaces",
|
||||
name: "list namespaces of cluster gondor",
|
||||
url: "/kapis/clusters/gondor/resources.kubesphere.io/v1alpha3/workspaces/workspace1/namespaces",
|
||||
method: http.MethodGet,
|
||||
expectedErr: nil,
|
||||
expectedVerb: "list",
|
||||
expectedResource: "namespaces",
|
||||
expectedIsResourceRequest: true,
|
||||
expectedWorkspace: "workspace1",
|
||||
expectedCluster: "cluster1",
|
||||
expectedCluster: "gondor",
|
||||
expectedKubernetesRequest: false,
|
||||
},
|
||||
{
|
||||
name: "list clusters",
|
||||
url: "/apis/cluster.kubesphere.io/v1alpha1/clusters",
|
||||
method: http.MethodGet,
|
||||
expectedErr: nil,
|
||||
expectedVerb: "list",
|
||||
expectedResource: "clusters",
|
||||
expectedIsResourceRequest: true,
|
||||
expectedWorkspace: "",
|
||||
expectedCluster: "",
|
||||
expectedKubernetesRequest: true,
|
||||
},
|
||||
{
|
||||
name: "get cluster gondor",
|
||||
url: "/apis/cluster.kubesphere.io/v1alpha1/clusters/gondor",
|
||||
method: http.MethodGet,
|
||||
expectedErr: nil,
|
||||
expectedVerb: "get",
|
||||
expectedResource: "clusters",
|
||||
expectedIsResourceRequest: true,
|
||||
expectedWorkspace: "",
|
||||
expectedCluster: "",
|
||||
expectedKubernetesRequest: true,
|
||||
},
|
||||
{
|
||||
name: "random query",
|
||||
url: "/foo/bar",
|
||||
|
||||
Reference in New Issue
Block a user