feat: kubesphere 4.0 (#6115)

* feat: kubesphere 4.0

Signed-off-by: ci-bot <ci-bot@kubesphere.io>

* feat: kubesphere 4.0

Signed-off-by: ci-bot <ci-bot@kubesphere.io>

---------

Signed-off-by: ci-bot <ci-bot@kubesphere.io>
Co-authored-by: ks-ci-bot <ks-ci-bot@example.com>
Co-authored-by: joyceliu <joyceliu@yunify.com>
This commit is contained in:
KubeSphere CI Bot
2024-09-06 11:05:52 +08:00
committed by GitHub
parent b5015ec7b9
commit 447a51f08b
8557 changed files with 546695 additions and 1146174 deletions

View File

@@ -19,8 +19,6 @@ package request
import (
"context"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apiserver/pkg/apis/audit"
"k8s.io/apiserver/pkg/authentication/user"
)
@@ -30,48 +28,15 @@ import (
type key int
const (
// namespaceKey is the context key for the request namespace.
namespaceKey key = iota
// userKey is the context key for the request user.
userKey
// auditKey is the context key for the audit event.
auditKey
userKey key = iota
)
// NewContext instantiates a base context object for request flows.
func NewContext() context.Context {
return context.TODO()
}
// NewDefaultContext instantiates a base context object for request flows in the default namespace
func NewDefaultContext() context.Context {
return WithNamespace(NewContext(), metav1.NamespaceDefault)
}
// WithValue returns a copy of parent in which the value associated with key is val.
func WithValue(parent context.Context, key interface{}, val interface{}) context.Context {
return context.WithValue(parent, key, val)
}
// WithNamespace returns a copy of parent in which the namespace value is set
func WithNamespace(parent context.Context, namespace string) context.Context {
return WithValue(parent, namespaceKey, namespace)
}
// NamespaceFrom returns the value of the namespace key on the ctx
func NamespaceFrom(ctx context.Context) (string, bool) {
namespace, ok := ctx.Value(namespaceKey).(string)
return namespace, ok
}
// NamespaceValue returns the value of the namespace key on the ctx, or the empty string if none
func NamespaceValue(ctx context.Context) string {
namespace, _ := NamespaceFrom(ctx)
return namespace
}
// WithUser returns a copy of parent in which the user value is set
func WithUser(parent context.Context, user user.Info) context.Context {
return WithValue(parent, userKey, user)
@@ -82,14 +47,3 @@ func UserFrom(ctx context.Context) (user.Info, bool) {
user, ok := ctx.Value(userKey).(user.Info)
return user, ok
}
// WithAuditEvent returns set audit event struct.
func WithAuditEvent(parent context.Context, ev *audit.Event) context.Context {
return WithValue(parent, auditKey, ev)
}
// AuditEventFrom returns the audit event struct on the ctx
func AuditEventFrom(ctx context.Context) *audit.Event {
ev, _ := ctx.Value(auditKey).(*audit.Event)
return ev
}

View File

@@ -17,35 +17,17 @@ limitations under the License.
package request
import (
"context"
"testing"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apiserver/pkg/authentication/user"
)
// Following code copied from k8s.io/apiserver/pkg/endpoints/request to avoid import collision
// TestNamespaceContext validates that a namespace can be get/set on a context object
func TestNamespaceContext(t *testing.T) {
ctx := NewDefaultContext()
result, ok := NamespaceFrom(ctx)
if !ok {
t.Fatalf("Error getting namespace")
}
if metav1.NamespaceDefault != result {
t.Fatalf("Expected: %s, Actual: %s", metav1.NamespaceDefault, result)
}
ctx = NewContext()
_, ok = NamespaceFrom(ctx)
if ok {
t.Fatalf("Should not be ok because there is no namespace on the context")
}
}
// TestUserContext validates that a userinfo can be get/set on a context object
func TestUserContext(t *testing.T) {
ctx := NewContext()
ctx := context.TODO()
_, ok := UserFrom(ctx)
if ok {
t.Fatalf("Should not be ok because there is no user.Info on the context")
@@ -91,5 +73,4 @@ func TestUserContext(t *testing.T) {
} else if actualExtra[expectedExtraKey][0] != expectedExtraValue {
t.Fatalf("Get user extra map value error, Expected: %s, Actual: %s", expectedExtraValue, actualExtra[expectedExtraKey])
}
}

View File

@@ -1,18 +1,7 @@
/*
Copyright 2019 The KubeSphere Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
* Please refer to the LICENSE file in the root directory of the project.
* https://github.com/kubesphere/kubesphere/blob/master/LICENSE
*/
// NOTE: This file is copied from k8s.io/apiserver/pkg/endpoints/request.
// We expanded requestInfo.
@@ -81,9 +70,6 @@ type RequestInfo struct {
// Cluster of requested resource, this is empty in single-cluster environment
Cluster string
// DevOps project of requested resource
DevOps string
// Scope of requested resource.
ResourceScope string
@@ -125,8 +111,8 @@ type RequestInfoFactory struct {
// /kapis/{api-group}/{version}/namespaces/{namespace}/{resource}
// /kapis/{api-group}/{version}/namespaces/{namespace}/{resource}/{resourceName}
// With workspaces:
// /kapis/clusters/{cluster}/{api-group}/{version}/namespaces/{namespace}/{resource}
// /kapis/clusters/{cluster}/{api-group}/{version}/namespaces/{namespace}/{resource}/{resourceName}
// /clusters/{cluster}/kapis/{api-group}/{version}/namespaces/{namespace}/{resource}
// /clusters/{cluster}/kapis/{api-group}/{version}/namespaces/{namespace}/{resource}/{resourceName}
func (r *RequestInfoFactory) NewRequestInfo(req *http.Request) (*RequestInfo, error) {
requestInfo := RequestInfo{
IsKubernetesRequest: false,
@@ -144,7 +130,7 @@ func (r *RequestInfoFactory) NewRequestInfo(req *http.Request) (*RequestInfo, er
prefix := requestInfo.APIPrefix
if prefix == "" {
currentParts := splitPath(requestInfo.Path)
//Proxy discovery API
// Proxy discovery API
if len(currentParts) > 0 && len(currentParts) < 3 {
prefix = currentParts[0]
}
@@ -159,6 +145,18 @@ func (r *RequestInfoFactory) NewRequestInfo(req *http.Request) (*RequestInfo, er
return &requestInfo, nil
}
// URL forms: /clusters/{cluster}/*
if currentParts[0] == "clusters" {
if len(currentParts) > 1 {
requestInfo.Cluster = currentParts[1]
// resolve the real path behind the cluster dispatcher
requestInfo.Path = strings.TrimPrefix(requestInfo.Path, fmt.Sprintf("/clusters/%s", requestInfo.Cluster))
}
if len(currentParts) > 2 {
currentParts = currentParts[2:]
}
}
if !r.APIPrefixes.Has(currentParts[0]) {
// return a non-resource request
return &requestInfo, nil
@@ -166,13 +164,19 @@ 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:]
// fallback to legacy cluster API
// TODO remove the following codes
if requestInfo.Cluster == "" {
// URL forms: /(kapis|apis|api)/clusters/{cluster}/*
if currentParts[0] == "clusters" {
if len(currentParts) > 1 {
requestInfo.Cluster = currentParts[1]
// resolve the real path behind the cluster dispatcher
requestInfo.Path = strings.Replace(requestInfo.Path, fmt.Sprintf("/clusters/%s", requestInfo.Cluster), "", 1)
}
if len(currentParts) > 2 {
currentParts = currentParts[2:]
}
}
}
@@ -216,7 +220,7 @@ func (r *RequestInfoFactory) NewRequestInfo(req *http.Request) (*RequestInfo, er
}
// URL forms: /workspaces/{workspace}/*
if currentParts[0] == "workspaces" {
if currentParts[0] == "workspaces" || currentParts[0] == "workspacetemplates" {
if len(currentParts) > 1 {
requestInfo.Workspace = currentParts[1]
}
@@ -230,25 +234,12 @@ func (r *RequestInfoFactory) NewRequestInfo(req *http.Request) (*RequestInfo, er
if len(currentParts) > 1 {
requestInfo.Namespace = currentParts[1]
// if there is another step after the namespace name and it is not a known namespace subresource
// if there is another step after the namespace name, and it is not a known namespace subresource
// move currentParts to include it as a resource in its own right
if len(currentParts) > 2 && !namespaceSubresources.Has(currentParts[2]) {
currentParts = currentParts[2:]
}
}
} else if currentParts[0] == "devops" {
if len(currentParts) > 1 {
requestInfo.DevOps = currentParts[1]
// if there is another step after the devops name
// move currentParts to include it as a resource in its own right
if len(currentParts) > 2 {
currentParts = currentParts[2:]
}
}
} else {
requestInfo.Namespace = metav1.NamespaceNone
requestInfo.DevOps = metav1.NamespaceNone
}
// parsing successful, so we now know the proper value for .Parts
@@ -327,7 +318,7 @@ type requestInfoKeyType int
const requestInfoKey requestInfoKeyType = iota
func WithRequestInfo(parent context.Context, info *RequestInfo) context.Context {
return k8srequest.WithValue(parent, requestInfoKey, info)
return context.WithValue(parent, requestInfoKey, info)
}
func RequestInfoFrom(ctx context.Context) (*RequestInfo, bool) {
@@ -349,12 +340,15 @@ const (
ClusterScope = "Cluster"
WorkspaceScope = "Workspace"
NamespaceScope = "Namespace"
DevOpsScope = "DevOps"
workspaceSelectorPrefix = constants.WorkspaceLabelKey + "="
)
func (r *RequestInfoFactory) resolveResourceScope(request RequestInfo) string {
if r.isGlobalScopeResource(request.APIGroup, request.Resource) {
// GET /apis/tenant.kubesphere.io/v1beta1/workspaces/{workspace}
if request.Workspace != "" {
return WorkspaceScope
}
return GlobalScope
}
@@ -362,10 +356,6 @@ func (r *RequestInfoFactory) resolveResourceScope(request RequestInfo) string {
return NamespaceScope
}
if request.DevOps != "" {
return DevOpsScope
}
if request.Workspace != "" {
return WorkspaceScope
}

View File

@@ -1,18 +1,7 @@
/*
Copyright 2020 The KubeSphere Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
* Please refer to the LICENSE file in the root directory of the project.
* https://github.com/kubesphere/kubesphere/blob/master/LICENSE
*/
package request
@@ -59,7 +48,7 @@ func TestRequestInfoFactory_NewRequestInfo(t *testing.T) {
},
{
name: "list clusterRoles of cluster gondor",
url: "/apis/clusters/gondor/rbac.authorization.k8s.io/v1/clusterroles",
url: "/clusters/gondor/apis/rbac.authorization.k8s.io/v1/clusterroles",
method: http.MethodGet,
expectedErr: nil,
expectedVerb: "list",
@@ -81,7 +70,7 @@ func TestRequestInfoFactory_NewRequestInfo(t *testing.T) {
},
{
name: "list nodes of cluster gondor",
url: "/api/clusters/gondor/v1/nodes",
url: "/clusters/gondor/api/v1/nodes",
method: http.MethodGet,
expectedErr: nil,
expectedVerb: "list",
@@ -92,7 +81,7 @@ func TestRequestInfoFactory_NewRequestInfo(t *testing.T) {
},
{
name: "list roles of cluster gondor",
url: "/apis/clusters/gondor/rbac.authorization.k8s.io/v1/namespaces/namespace1/roles",
url: "/clusters/gondor/apis/rbac.authorization.k8s.io/v1/namespaces/namespace1/roles",
method: http.MethodGet,
expectedErr: nil,
expectedVerb: "list",
@@ -128,7 +117,7 @@ func TestRequestInfoFactory_NewRequestInfo(t *testing.T) {
},
{
name: "list namespaces of cluster gondor",
url: "/kapis/clusters/gondor/resources.kubesphere.io/v1alpha3/workspaces/workspace1/namespaces",
url: "/clusters/gondor/kapis/resources.kubesphere.io/v1alpha3/workspaces/workspace1/namespaces",
method: http.MethodGet,
expectedErr: nil,
expectedVerb: "list",