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

File diff suppressed because it is too large Load Diff

View File

@@ -1,18 +1,7 @@
/*
Copyright 2020 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 group
@@ -21,48 +10,45 @@ import (
"encoding/json"
"fmt"
"github.com/Masterminds/semver/v3"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/validation/field"
"k8s.io/client-go/kubernetes"
"k8s.io/klog/v2"
iamv1alpha2 "kubesphere.io/api/iam/v1alpha2"
tenantv1alpha1 "kubesphere.io/api/tenant/v1alpha1"
"k8s.io/utils/ptr"
iamv1beta1 "kubesphere.io/api/iam/v1beta1"
"kubesphere.io/api/tenant/v1beta1"
runtimeclient "sigs.k8s.io/controller-runtime/pkg/client"
"kubesphere.io/kubesphere/pkg/api"
"kubesphere.io/kubesphere/pkg/apiserver/query"
kubesphere "kubesphere.io/kubesphere/pkg/client/clientset/versioned"
"kubesphere.io/kubesphere/pkg/informers"
resourcesv1alpha3 "kubesphere.io/kubesphere/pkg/models/resources/v1alpha3/resource"
)
type GroupOperator interface {
ListGroups(workspace string, queryParam *query.Query) (*api.ListResult, error)
CreateGroup(workspace string, namespace *iamv1alpha2.Group) (*iamv1alpha2.Group, error)
DescribeGroup(workspace, group string) (*iamv1alpha2.Group, error)
CreateGroup(workspace string, namespace *iamv1beta1.Group) (*iamv1beta1.Group, error)
DescribeGroup(workspace, group string) (*iamv1beta1.Group, error)
DeleteGroup(workspace, group string) error
UpdateGroup(workspace string, group *iamv1alpha2.Group) (*iamv1alpha2.Group, error)
PatchGroup(workspace string, group *iamv1alpha2.Group) (*iamv1alpha2.Group, error)
UpdateGroup(workspace string, group *iamv1beta1.Group) (*iamv1beta1.Group, error)
PatchGroup(workspace string, group *iamv1beta1.Group) (*iamv1beta1.Group, error)
DeleteGroupBinding(workspace, name string) error
CreateGroupBinding(workspace, groupName, userName string) (*iamv1alpha2.GroupBinding, error)
CreateGroupBinding(workspace, groupName, userName string) (*iamv1beta1.GroupBinding, error)
ListGroupBindings(workspace string, queryParam *query.Query) (*api.ListResult, error)
}
type groupOperator struct {
k8sclient kubernetes.Interface
ksclient kubesphere.Interface
resourceGetter *resourcesv1alpha3.ResourceGetter
client runtimeclient.Client
resourceGetter *resourcesv1alpha3.Getter
}
func New(informers informers.InformerFactory, ksclient kubesphere.Interface, k8sclient kubernetes.Interface) GroupOperator {
func New(cacheClient runtimeclient.Client, k8sVersion *semver.Version) GroupOperator {
return &groupOperator{
resourceGetter: resourcesv1alpha3.NewResourceGetter(informers, nil),
k8sclient: k8sclient,
ksclient: ksclient,
resourceGetter: resourcesv1alpha3.NewResourceGetter(cacheClient, k8sVersion),
client: cacheClient,
}
}
@@ -70,7 +56,7 @@ func (t *groupOperator) ListGroups(workspace string, queryParam *query.Query) (*
if workspace != "" {
// filter by workspace
queryParam.Filters[query.FieldLabel] = query.Value(fmt.Sprintf("%s=%s", tenantv1alpha1.WorkspaceLabel, workspace))
queryParam.Filters[query.FieldLabel] = query.Value(fmt.Sprintf("%s=%s", v1beta1.WorkspaceLabel, workspace))
}
result, err := t.resourceGetter.List("groups", "", queryParam)
@@ -82,10 +68,10 @@ func (t *groupOperator) ListGroups(workspace string, queryParam *query.Query) (*
}
// CreateGroup adds a workspace label to group which indicates group is under the workspace
func (t *groupOperator) CreateGroup(workspace string, group *iamv1alpha2.Group) (*iamv1alpha2.Group, error) {
func (t *groupOperator) CreateGroup(workspace string, group *iamv1beta1.Group) (*iamv1beta1.Group, error) {
if group.GenerateName == "" {
err := errors.NewInvalid(iamv1alpha2.SchemeGroupVersion.WithKind(iamv1alpha2.ResourcePluralGroup).GroupKind(),
err := errors.NewInvalid(iamv1beta1.SchemeGroupVersion.WithKind(iamv1beta1.ResourcePluralGroup).GroupKind(),
"", []*field.Error{field.Required(field.NewPath("metadata.generateName"), "generateName is required")})
klog.Error(err)
return nil, err
@@ -95,13 +81,14 @@ func (t *groupOperator) CreateGroup(workspace string, group *iamv1alpha2.Group)
if unique, err := t.isGenerateNameUnique(workspace, group.GenerateName); err != nil {
return nil, err
} else if !unique {
err = errors.NewConflict(iamv1alpha2.Resource(iamv1alpha2.ResourcePluralGroup),
err = errors.NewConflict(iamv1beta1.Resource(iamv1beta1.ResourcePluralGroup),
group.GenerateName, fmt.Errorf("a group named %s already exists in the workspace", group.GenerateName))
klog.Error(err)
return nil, err
}
return t.ksclient.IamV1alpha2().Groups().Create(context.Background(), labelGroupWithWorkspaceName(group, workspace), metav1.CreateOptions{})
group = labelGroupWithWorkspaceName(group, workspace)
return group, t.client.Create(context.Background(), group)
}
func (t *groupOperator) isGenerateNameUnique(workspace, generateName string) (bool, error) {
@@ -113,7 +100,7 @@ func (t *groupOperator) isGenerateNameUnique(workspace, generateName string) (bo
return false, err
}
for _, obj := range result.Items {
g := obj.(*iamv1alpha2.Group)
g := obj.(*iamv1beta1.Group)
if g.GenerateName == generateName {
return false, err
}
@@ -121,13 +108,13 @@ func (t *groupOperator) isGenerateNameUnique(workspace, generateName string) (bo
return true, nil
}
func (t *groupOperator) DescribeGroup(workspace, group string) (*iamv1alpha2.Group, error) {
func (t *groupOperator) DescribeGroup(workspace, group string) (*iamv1beta1.Group, error) {
obj, err := t.resourceGetter.Get("groups", "", group)
if err != nil {
return nil, err
}
ns := obj.(*iamv1alpha2.Group)
if ns.Labels[tenantv1alpha1.WorkspaceLabel] != workspace {
ns := obj.(*iamv1beta1.Group)
if ns.Labels[v1beta1.WorkspaceLabel] != workspace {
err := errors.NewNotFound(corev1.Resource("group"), group)
klog.Error(err)
return nil, err
@@ -135,73 +122,72 @@ func (t *groupOperator) DescribeGroup(workspace, group string) (*iamv1alpha2.Gro
return ns, nil
}
func (t *groupOperator) DeleteGroup(workspace, group string) error {
_, err := t.DescribeGroup(workspace, group)
func (t *groupOperator) DeleteGroup(workspace, groupName string) error {
group, err := t.DescribeGroup(workspace, groupName)
if err != nil {
return err
}
return t.ksclient.IamV1alpha2().Groups().Delete(context.Background(), group, *metav1.NewDeleteOptions(0))
return t.client.Delete(context.Background(), group, &runtimeclient.DeleteOptions{GracePeriodSeconds: ptr.To[int64](0)})
}
func (t *groupOperator) UpdateGroup(workspace string, group *iamv1alpha2.Group) (*iamv1alpha2.Group, error) {
func (t *groupOperator) UpdateGroup(workspace string, group *iamv1beta1.Group) (*iamv1beta1.Group, error) {
_, err := t.DescribeGroup(workspace, group.Name)
if err != nil {
return nil, err
}
group = labelGroupWithWorkspaceName(group, workspace)
return t.ksclient.IamV1alpha2().Groups().Update(context.Background(), group, metav1.UpdateOptions{})
return group, t.client.Update(context.Background(), group)
}
func (t *groupOperator) PatchGroup(workspace string, group *iamv1alpha2.Group) (*iamv1alpha2.Group, error) {
_, err := t.DescribeGroup(workspace, group.Name)
func (t *groupOperator) PatchGroup(workspace string, group *iamv1beta1.Group) (*iamv1beta1.Group, error) {
group, err := t.DescribeGroup(workspace, group.Name)
if err != nil {
return nil, err
}
if group.Labels != nil {
group.Labels[tenantv1alpha1.WorkspaceLabel] = workspace
group.Labels[v1beta1.WorkspaceLabel] = workspace
}
data, err := json.Marshal(group)
if err != nil {
return nil, err
}
return t.ksclient.IamV1alpha2().Groups().Patch(context.Background(), group.Name, types.MergePatchType, data, metav1.PatchOptions{})
return group, t.client.Patch(context.Background(), group, runtimeclient.RawPatch(types.MergePatchType, data))
}
func (t *groupOperator) DeleteGroupBinding(workspace, name string) error {
obj, err := t.resourceGetter.Get("groupbindings", "", name)
if err != nil {
groupBinding := &iamv1beta1.GroupBinding{}
if err := t.client.Get(context.Background(), types.NamespacedName{Name: name}, groupBinding); err != nil {
return err
}
ns := obj.(*iamv1alpha2.GroupBinding)
if ns.Labels[tenantv1alpha1.WorkspaceLabel] != workspace {
err := errors.NewNotFound(corev1.Resource("groupbinding"), name)
if groupBinding.Labels[v1beta1.WorkspaceLabel] != workspace {
err := errors.NewNotFound(corev1.Resource("groupbindings"), name)
klog.Error(err)
return err
}
return t.ksclient.IamV1alpha2().GroupBindings().Delete(context.Background(), name, *metav1.NewDeleteOptions(0))
return t.client.Delete(context.Background(), groupBinding, &runtimeclient.DeleteOptions{GracePeriodSeconds: ptr.To[int64](0)})
}
func (t *groupOperator) CreateGroupBinding(workspace, groupName, userName string) (*iamv1alpha2.GroupBinding, error) {
func (t *groupOperator) CreateGroupBinding(workspace, groupName, userName string) (*iamv1beta1.GroupBinding, error) {
groupBinding := iamv1alpha2.GroupBinding{
groupBinding := &iamv1beta1.GroupBinding{
ObjectMeta: metav1.ObjectMeta{
GenerateName: fmt.Sprintf("%s-%s-", groupName, userName),
Labels: map[string]string{
iamv1alpha2.UserReferenceLabel: userName,
iamv1alpha2.GroupReferenceLabel: groupName,
tenantv1alpha1.WorkspaceLabel: workspace,
iamv1beta1.UserReferenceLabel: userName,
iamv1beta1.GroupReferenceLabel: groupName,
v1beta1.WorkspaceLabel: workspace,
},
},
Users: []string{userName},
GroupRef: iamv1alpha2.GroupRef{
APIGroup: iamv1alpha2.SchemeGroupVersion.Group,
Kind: iamv1alpha2.ResourcePluralGroup,
GroupRef: iamv1beta1.GroupRef{
APIGroup: iamv1beta1.SchemeGroupVersion.Group,
Kind: iamv1beta1.ResourcePluralGroup,
Name: groupName,
},
}
return t.ksclient.IamV1alpha2().GroupBindings().Create(context.Background(), &groupBinding, metav1.CreateOptions{})
return groupBinding, t.client.Create(context.Background(), groupBinding)
}
func (t *groupOperator) ListGroupBindings(workspace string, query *query.Query) (*api.ListResult, error) {
@@ -212,7 +198,7 @@ func (t *groupOperator) ListGroupBindings(workspace string, query *query.Query)
return nil, err
}
// workspace resources must be filtered by workspace
wsSelector := labels.Set{tenantv1alpha1.WorkspaceLabel: workspace}
wsSelector := labels.Set{v1beta1.WorkspaceLabel: workspace}
query.LabelSelector = labels.Merge(lableSelector, wsSelector).String()
result, err := t.resourceGetter.List("groupbindings", "", query)
@@ -225,12 +211,12 @@ func (t *groupOperator) ListGroupBindings(workspace string, query *query.Query)
// labelGroupWithWorkspaceName adds a kubesphere.io/workspace=[workspaceName] label to namespace which
// indicates namespace is under the workspace
func labelGroupWithWorkspaceName(namespace *iamv1alpha2.Group, workspaceName string) *iamv1alpha2.Group {
func labelGroupWithWorkspaceName(namespace *iamv1beta1.Group, workspaceName string) *iamv1beta1.Group {
if namespace.Labels == nil {
namespace.Labels = make(map[string]string, 0)
}
namespace.Labels[tenantv1alpha1.WorkspaceLabel] = workspaceName // label namespace with workspace name
namespace.Labels[v1beta1.WorkspaceLabel] = workspaceName // label namespace with workspace name
return namespace
}

View File

@@ -1,69 +1,59 @@
/*
Copyright 2020 The KubeSphere Authors.
* Please refer to the LICENSE file in the root directory of the project.
* https://github.com/kubesphere/kubesphere/blob/master/LICENSE
*/
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.
*/
package im
import (
"context"
"fmt"
"strconv"
"time"
"kubesphere.io/kubesphere/pkg/apiserver/authentication"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
"k8s.io/klog/v2"
iamv1alpha2 "kubesphere.io/api/iam/v1alpha2"
"k8s.io/utils/ptr"
iamv1beta1 "kubesphere.io/api/iam/v1beta1"
runtimeclient "sigs.k8s.io/controller-runtime/pkg/client"
"kubesphere.io/kubesphere/pkg/api"
"kubesphere.io/kubesphere/pkg/apiserver/authentication"
"kubesphere.io/kubesphere/pkg/apiserver/query"
kubesphere "kubesphere.io/kubesphere/pkg/client/clientset/versioned"
"kubesphere.io/kubesphere/pkg/models/auth"
resources "kubesphere.io/kubesphere/pkg/models/resources/v1alpha3"
resourcev1beta1 "kubesphere.io/kubesphere/pkg/models/resources/v1beta1"
)
type IdentityManagementInterface interface {
CreateUser(user *iamv1alpha2.User) (*iamv1alpha2.User, error)
CreateUser(user *iamv1beta1.User) (*iamv1beta1.User, error)
ListUsers(query *query.Query) (*api.ListResult, error)
DeleteUser(username string) error
UpdateUser(user *iamv1alpha2.User) (*iamv1alpha2.User, error)
DescribeUser(username string) (*iamv1alpha2.User, error)
UpdateUser(user *iamv1beta1.User) (*iamv1beta1.User, error)
DescribeUser(username string) (*iamv1beta1.User, error)
ModifyPassword(username string, password string) error
ListLoginRecords(username string, query *query.Query) (*api.ListResult, error)
PasswordVerify(username string, password string) error
}
func NewOperator(ksClient kubesphere.Interface, userGetter resources.Interface, loginRecordGetter resources.Interface, options *authentication.Options) IdentityManagementInterface {
func NewOperator(client runtimeclient.Client, resourceManager resourcev1beta1.ResourceManager, options *authentication.Options) IdentityManagementInterface {
im := &imOperator{
ksClient: ksClient,
userGetter: userGetter,
loginRecordGetter: loginRecordGetter,
options: options,
client: client,
options: options,
resourceManager: resourceManager,
}
return im
}
type imOperator struct {
ksClient kubesphere.Interface
userGetter resources.Interface
loginRecordGetter resources.Interface
options *authentication.Options
client runtimeclient.Client
resourceManager resourcev1beta1.ResourceManager
options *authentication.Options
}
// UpdateUser returns user information after update.
func (im *imOperator) UpdateUser(new *iamv1alpha2.User) (*iamv1alpha2.User, error) {
func (im *imOperator) UpdateUser(new *iamv1beta1.User) (*iamv1beta1.User, error) {
old, err := im.fetch(new.Name)
if err != nil {
klog.Error(err)
@@ -73,109 +63,108 @@ func (im *imOperator) UpdateUser(new *iamv1alpha2.User) (*iamv1alpha2.User, erro
new.Spec.EncryptedPassword = old.Spec.EncryptedPassword
status := old.Status
// only support enable or disable
if new.Status.State == iamv1alpha2.UserDisabled || new.Status.State == iamv1alpha2.UserActive {
if new.Status.State == iamv1beta1.UserDisabled || new.Status.State == iamv1beta1.UserActive {
status.State = new.Status.State
status.LastTransitionTime = &metav1.Time{Time: time.Now()}
}
new.Status = status
updated, err := im.ksClient.IamV1alpha2().Users().Update(context.Background(), new, metav1.UpdateOptions{})
if err != nil {
klog.Error(err)
if err := im.client.Update(context.Background(), new); err != nil {
return nil, err
}
return ensurePasswordNotOutput(updated), nil
new = new.DeepCopy()
new.Spec.EncryptedPassword = ""
return new, nil
}
func (im *imOperator) fetch(username string) (*iamv1alpha2.User, error) {
obj, err := im.userGetter.Get("", username)
if err != nil {
klog.Error(err)
func (im *imOperator) fetch(username string) (*iamv1beta1.User, error) {
user := &iamv1beta1.User{}
if err := im.client.Get(context.Background(), types.NamespacedName{Name: username}, user); err != nil {
return nil, err
}
user := obj.(*iamv1alpha2.User).DeepCopy()
return user, nil
return user.DeepCopy(), nil
}
func (im *imOperator) ModifyPassword(username string, password string) error {
user, err := im.fetch(username)
if err != nil {
klog.Error(err)
return err
}
user.Spec.EncryptedPassword = password
_, err = im.ksClient.IamV1alpha2().Users().Update(context.Background(), user, metav1.UpdateOptions{})
if err != nil {
klog.Error(err)
if err := im.client.Update(context.Background(), user); err != nil {
return err
}
return nil
}
func (im *imOperator) ListUsers(query *query.Query) (result *api.ListResult, err error) {
result, err = im.userGetter.List("", query)
func (im *imOperator) ListUsers(query *query.Query) (*api.ListResult, error) {
result, err := im.resourceManager.ListResources(context.Background(), iamv1beta1.SchemeGroupVersion.WithResource(iamv1beta1.ResourcesPluralUser), "", query)
if err != nil {
klog.Error(err)
return nil, err
}
items := make([]interface{}, 0)
for _, item := range result.Items {
user := item.(*iamv1alpha2.User)
out := ensurePasswordNotOutput(user)
items := make([]runtime.Object, 0)
userList := result.(*iamv1beta1.UserList)
for _, item := range userList.Items {
out := item.DeepCopy()
out.Spec.EncryptedPassword = ""
items = append(items, out)
}
result.Items = items
return result, nil
total, err := strconv.ParseInt(userList.GetContinue(), 10, 64)
if err != nil {
return nil, err
}
return &api.ListResult{Items: items, TotalItems: int(total)}, nil
}
func (im *imOperator) PasswordVerify(username string, password string) error {
obj, err := im.userGetter.Get("", username)
user, err := im.fetch(username)
if err != nil {
klog.Error(err)
return err
}
user := obj.(*iamv1alpha2.User)
if err = auth.PasswordVerify(user.Spec.EncryptedPassword, password); err != nil {
return err
}
return nil
}
func (im *imOperator) DescribeUser(username string) (*iamv1alpha2.User, error) {
obj, err := im.userGetter.Get("", username)
func (im *imOperator) DescribeUser(username string) (*iamv1beta1.User, error) {
user, err := im.fetch(username)
if err != nil {
klog.Error(err)
return nil, err
}
user := obj.(*iamv1alpha2.User)
return ensurePasswordNotOutput(user), nil
out := user.DeepCopy()
out.Spec.EncryptedPassword = ""
return out, nil
}
func (im *imOperator) DeleteUser(username string) error {
return im.ksClient.IamV1alpha2().Users().Delete(context.Background(), username, *metav1.NewDeleteOptions(0))
user, err := im.fetch(username)
if err != nil {
return err
}
return im.client.Delete(context.Background(), user, &runtimeclient.DeleteOptions{GracePeriodSeconds: ptr.To[int64](0)})
}
func (im *imOperator) CreateUser(user *iamv1alpha2.User) (*iamv1alpha2.User, error) {
user, err := im.ksClient.IamV1alpha2().Users().Create(context.Background(), user, metav1.CreateOptions{})
if err != nil {
klog.Error(err)
func (im *imOperator) CreateUser(user *iamv1beta1.User) (*iamv1beta1.User, error) {
if err := im.client.Create(context.Background(), user); err != nil {
return nil, err
}
return user, nil
}
func (im *imOperator) ListLoginRecords(username string, q *query.Query) (*api.ListResult, error) {
q.Filters[query.FieldLabel] = query.Value(fmt.Sprintf("%s=%s", iamv1alpha2.UserReferenceLabel, username))
result, err := im.loginRecordGetter.List("", q)
q.Filters[query.FieldLabel] = query.Value(fmt.Sprintf("%s=%s", iamv1beta1.UserReferenceLabel, username))
result, err := im.resourceManager.ListResources(context.Background(), iamv1beta1.SchemeGroupVersion.WithResource(iamv1beta1.ResourcesPluralLoginRecord), "", q)
if err != nil {
klog.Error(err)
return nil, err
}
return result, nil
}
func ensurePasswordNotOutput(user *iamv1alpha2.User) *iamv1alpha2.User {
out := user.DeepCopy()
// ensure encrypted password will not be output
out.Spec.EncryptedPassword = ""
return out
items := make([]runtime.Object, 0)
userList := result.(*iamv1beta1.LoginRecordList)
for _, item := range userList.Items {
items = append(items, item.DeepCopy())
}
total, err := strconv.ParseInt(userList.GetContinue(), 10, 64)
if err != nil {
return nil, err
}
return &api.ListResult{Items: items, TotalItems: int(total)}, nil
}

View File

@@ -1,17 +1,6 @@
/*
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 im