mirror of
https://github.com/labring/sealos.git
synced 2026-11-03 12:46:33 +08:00
feature(main): add usergroups controller for user (#1600)
Signed-off-by: cuisongliu <cuisongliu@qq.com> Signed-off-by: cuisongliu <cuisongliu@qq.com>
This commit is contained in:
@@ -37,7 +37,8 @@ type UserSpec struct {
|
||||
// The minimum valid value for expirationSeconds is 600, i.e. 10 minutes.
|
||||
//
|
||||
// +optional
|
||||
CSRExpirationSeconds *int32 `json:"CSRExpirationSeconds,omitempty"`
|
||||
//+kubebuilder:default:=7200
|
||||
CSRExpirationSeconds int32 `json:"csrExpirationSeconds,omitempty"`
|
||||
}
|
||||
type UserPhase string
|
||||
|
||||
@@ -54,6 +55,8 @@ type UserStatus struct {
|
||||
//+kubebuilder:default:=UserUnknown
|
||||
Phase UserPhase `json:"phase,omitempty"`
|
||||
KubeConfig string `json:"kubeConfig"`
|
||||
//+kubebuilder:default:=7200
|
||||
ObservedCSRExpirationSeconds int32 `json:"observedCSRExpirationSeconds,omitempty"`
|
||||
// The generation observed by the user controller.
|
||||
// +optional
|
||||
ObservedGeneration int64 `json:"observedGeneration,omitempty"`
|
||||
@@ -91,6 +94,7 @@ type Condition struct {
|
||||
// +kubebuilder:resource:scope=Cluster
|
||||
// +kubebuilder:printcolumn:name="DisplayName",type="string",JSONPath=".spec.displayName"
|
||||
// +kubebuilder:printcolumn:name="Phase",type="string",JSONPath=".status.phase"
|
||||
// +kubebuilder:printcolumn:name="ExpirationSeconds",type="integer",JSONPath=".status.observedCSRExpirationSeconds"
|
||||
// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp"
|
||||
|
||||
// User is the Schema for the users API
|
||||
|
||||
@@ -26,12 +26,26 @@ import (
|
||||
// +kubebuilder:object:root=true
|
||||
// +kubebuilder:subresource:status
|
||||
// +kubebuilder:resource:scope=Cluster,shortName=ug
|
||||
// +kubebuilder:printcolumn:name="Phase",type="string",JSONPath=".status.phase"
|
||||
// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp"
|
||||
|
||||
// UserGroup is the Schema for the usergroups API
|
||||
type UserGroup struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ObjectMeta `json:"metadata,omitempty"`
|
||||
Status UserGroupStatus `json:"status,omitempty"`
|
||||
}
|
||||
|
||||
// UserGroupStatus defines the observed state of UserGroup
|
||||
type UserGroupStatus struct {
|
||||
// Phase is the recently observed lifecycle phase of user
|
||||
//+kubebuilder:default:=UserUnknown
|
||||
Phase UserPhase `json:"phase,omitempty"`
|
||||
// The generation observed by the user controller.
|
||||
// +optional
|
||||
ObservedGeneration int64 `json:"observedGeneration,omitempty"`
|
||||
// Conditions contains the different condition statuses for this user group.
|
||||
Conditions []Condition `json:"conditions,omitempty"`
|
||||
}
|
||||
|
||||
//+kubebuilder:object:root=true
|
||||
|
||||
@@ -247,7 +247,7 @@ func (in *User) DeepCopyInto(out *User) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
|
||||
in.Spec.DeepCopyInto(&out.Spec)
|
||||
out.Spec = in.Spec
|
||||
in.Status.DeepCopyInto(&out.Status)
|
||||
}
|
||||
|
||||
@@ -274,6 +274,7 @@ func (in *UserGroup) DeepCopyInto(out *UserGroup) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
|
||||
in.Status.DeepCopyInto(&out.Status)
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new UserGroup.
|
||||
@@ -389,6 +390,28 @@ func (in *UserGroupList) DeepCopyObject() runtime.Object {
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *UserGroupStatus) DeepCopyInto(out *UserGroupStatus) {
|
||||
*out = *in
|
||||
if in.Conditions != nil {
|
||||
in, out := &in.Conditions, &out.Conditions
|
||||
*out = make([]Condition, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new UserGroupStatus.
|
||||
func (in *UserGroupStatus) DeepCopy() *UserGroupStatus {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(UserGroupStatus)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *UserList) DeepCopyInto(out *UserList) {
|
||||
*out = *in
|
||||
@@ -424,11 +447,6 @@ func (in *UserList) DeepCopyObject() runtime.Object {
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *UserSpec) DeepCopyInto(out *UserSpec) {
|
||||
*out = *in
|
||||
if in.CSRExpirationSeconds != nil {
|
||||
in, out := &in.CSRExpirationSeconds, &out.CSRExpirationSeconds
|
||||
*out = new(int32)
|
||||
**out = **in
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new UserSpec.
|
||||
|
||||
@@ -18,6 +18,9 @@ spec:
|
||||
scope: Cluster
|
||||
versions:
|
||||
- additionalPrinterColumns:
|
||||
- jsonPath: .status.phase
|
||||
name: Phase
|
||||
type: string
|
||||
- jsonPath: .metadata.creationTimestamp
|
||||
name: Age
|
||||
type: date
|
||||
@@ -38,6 +41,52 @@ spec:
|
||||
type: string
|
||||
metadata:
|
||||
type: object
|
||||
status:
|
||||
description: UserGroupStatus defines the observed state of UserGroup
|
||||
properties:
|
||||
conditions:
|
||||
description: Conditions contains the different condition statuses
|
||||
for this user group.
|
||||
items:
|
||||
properties:
|
||||
lastHeartbeatTime:
|
||||
description: LastHeartbeatTime is the last time this condition
|
||||
was updated.
|
||||
format: date-time
|
||||
type: string
|
||||
lastTransitionTime:
|
||||
description: LastTransitionTime is the last time the condition
|
||||
changed from one status to another.
|
||||
format: date-time
|
||||
type: string
|
||||
message:
|
||||
description: Message is a human-readable message indicating
|
||||
details about the last status change.
|
||||
type: string
|
||||
reason:
|
||||
description: Reason is a (brief) reason for the condition's
|
||||
last status change.
|
||||
type: string
|
||||
status:
|
||||
description: Status is the status of the condition. One of True,
|
||||
False, Unknown.
|
||||
type: string
|
||||
type:
|
||||
type: string
|
||||
required:
|
||||
- status
|
||||
- type
|
||||
type: object
|
||||
type: array
|
||||
observedGeneration:
|
||||
description: The generation observed by the user controller.
|
||||
format: int64
|
||||
type: integer
|
||||
phase:
|
||||
default: UserUnknown
|
||||
description: Phase is the recently observed lifecycle phase of user
|
||||
type: string
|
||||
type: object
|
||||
type: object
|
||||
served: true
|
||||
storage: true
|
||||
|
||||
@@ -22,6 +22,9 @@ spec:
|
||||
- jsonPath: .status.phase
|
||||
name: Phase
|
||||
type: string
|
||||
- jsonPath: .status.observedCSRExpirationSeconds
|
||||
name: ExpirationSeconds
|
||||
type: integer
|
||||
- jsonPath: .metadata.creationTimestamp
|
||||
name: Age
|
||||
type: date
|
||||
@@ -45,7 +48,8 @@ spec:
|
||||
spec:
|
||||
description: UserSpec defines the desired state of User
|
||||
properties:
|
||||
CSRExpirationSeconds:
|
||||
csrExpirationSeconds:
|
||||
default: 7200
|
||||
description: "expirationSeconds is the requested duration of validity
|
||||
of the issued certificate. The certificate signer may issue a certificate
|
||||
with a different validity duration so a client must check the delta
|
||||
@@ -99,6 +103,10 @@ spec:
|
||||
type: array
|
||||
kubeConfig:
|
||||
type: string
|
||||
observedCSRExpirationSeconds:
|
||||
default: 7200
|
||||
format: int32
|
||||
type: integer
|
||||
observedGeneration:
|
||||
description: The generation observed by the user controller.
|
||||
format: int64
|
||||
|
||||
@@ -5,6 +5,26 @@ metadata:
|
||||
creationTimestamp: null
|
||||
name: manager-role
|
||||
rules:
|
||||
- apiGroups:
|
||||
- certificates.k8s.io
|
||||
resources:
|
||||
- certificatesigningrequests
|
||||
verbs:
|
||||
- create
|
||||
- delete
|
||||
- get
|
||||
- list
|
||||
- patch
|
||||
- update
|
||||
- watch
|
||||
- apiGroups:
|
||||
- certificates.k8s.io
|
||||
resources:
|
||||
- certificatesigningrequests/status
|
||||
verbs:
|
||||
- get
|
||||
- patch
|
||||
- update
|
||||
- apiGroups:
|
||||
- user.sealos.io
|
||||
resources:
|
||||
|
||||
@@ -4,3 +4,4 @@ metadata:
|
||||
name: f8699ded-58d3-432b-a9ff-56568b57a38d
|
||||
spec:
|
||||
displayName: cuisongliu
|
||||
csrExpirationSeconds: 1000000000
|
||||
|
||||
@@ -30,17 +30,18 @@ import (
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
|
||||
userv1 "github.com/labring/sealos/controllers/user/api/v1"
|
||||
"k8s.io/apimachinery/pkg/watch"
|
||||
"k8s.io/client-go/util/retry"
|
||||
|
||||
csrv1 "k8s.io/api/certificates/v1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/watch"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/rest"
|
||||
"k8s.io/client-go/tools/clientcmd/api"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
)
|
||||
|
||||
// newPrivateKey creates an RSA private key
|
||||
@@ -133,28 +134,49 @@ func (csr *CSR) updateCsr(config *rest.Config, cli client.Client) error {
|
||||
csrResource := &csrv1.CertificateSigningRequest{}
|
||||
csrResource.Name = csrName
|
||||
|
||||
deleteCSR := csrResource.DeepCopy()
|
||||
_ = cli.Delete(context.TODO(), deleteCSR, client.PropagationPolicy(v1.DeletePropagationBackground))
|
||||
user := &userv1.User{}
|
||||
err := cli.Get(context.TODO(), client.ObjectKey{Name: csr.User}, user)
|
||||
if err == nil {
|
||||
ref := v1.OwnerReference{
|
||||
APIVersion: userv1.GroupVersion.String(),
|
||||
Kind: "User",
|
||||
UID: user.GetUID(),
|
||||
Name: user.GetName(),
|
||||
}
|
||||
csrResource.OwnerReferences = append(csrResource.OwnerReferences, ref)
|
||||
csrName = fmt.Sprintf("%s-%d", csrName, user.Generation)
|
||||
csrResource.Name = csrName
|
||||
label = map[string]string{
|
||||
"csr-name": csrName,
|
||||
}
|
||||
}
|
||||
|
||||
change, err := controllerutil.CreateOrUpdate(context.TODO(), cli, csrResource, func() error {
|
||||
csrResource.Labels = label
|
||||
csrResource.Spec.Request = csr.data.TLSCsr
|
||||
csrResource.Spec.SignerName = csrv1.KubeAPIServerClientSignerName
|
||||
csrResource.Spec.ExpirationSeconds = &csr.ExpirationSeconds
|
||||
csrResource.Spec.Groups = []string{"system:authenticated"}
|
||||
csrResource.Spec.Usages = []csrv1.KeyUsage{
|
||||
if err = retry.RetryOnConflict(retry.DefaultRetry, func() error {
|
||||
insertCSR := csrResource.DeepCopy()
|
||||
insertCSR.Labels = label
|
||||
insertCSR.ResourceVersion = "0"
|
||||
insertCSR.Spec.Request = csr.data.TLSCsr
|
||||
insertCSR.Spec.SignerName = csrv1.KubeAPIServerClientSignerName
|
||||
insertCSR.Spec.ExpirationSeconds = &csr.ExpirationSeconds
|
||||
insertCSR.Spec.Groups = []string{"system:authenticated"}
|
||||
insertCSR.Spec.Usages = []csrv1.KeyUsage{
|
||||
"digital signature",
|
||||
"key encipherment",
|
||||
"client auth",
|
||||
}
|
||||
|
||||
err = cli.Create(context.TODO(), insertCSR)
|
||||
if err != nil {
|
||||
if !errors.IsAlreadyExists(err) {
|
||||
return err
|
||||
}
|
||||
}
|
||||
csrResource = insertCSR.DeepCopy()
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if change != controllerutil.OperationResultCreated {
|
||||
return errors.NewBadRequest("create csr failed,must create new csr for new user")
|
||||
}
|
||||
|
||||
clientset, err := kubernetes.NewForConfig(config)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -19,6 +19,7 @@ package helper
|
||||
import (
|
||||
v1 "github.com/labring/sealos/controllers/user/api/v1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
func IsConditionTrue(conditions []v1.Condition, condition v1.Condition) bool {
|
||||
@@ -78,3 +79,10 @@ func DeleteCondition(conditions []v1.Condition, conditionType v1.ConditionType)
|
||||
conditions = newConditions
|
||||
return conditions
|
||||
}
|
||||
|
||||
func SetConditionError(condition *v1.Condition, reason string, err error) {
|
||||
condition.LastHeartbeatTime = metav1.Now()
|
||||
condition.Status = corev1.ConditionFalse
|
||||
condition.Reason = reason
|
||||
condition.Message = err.Error()
|
||||
}
|
||||
|
||||
@@ -134,10 +134,10 @@ users:
|
||||
user := &v1.User{}
|
||||
user.Name = "cuisongliu"
|
||||
defaultExpirationDuration := int32(100000000)
|
||||
user.Spec.CSRExpirationSeconds = &defaultExpirationDuration
|
||||
user.Spec.CSRExpirationSeconds = defaultExpirationDuration
|
||||
c := &helper.Config{
|
||||
User: user.Name,
|
||||
ExpirationSeconds: *user.Spec.CSRExpirationSeconds,
|
||||
ExpirationSeconds: user.Spec.CSRExpirationSeconds,
|
||||
}
|
||||
|
||||
config, err := helper.NewGenerate(c).KubeConfig(cfg, k8sClient)
|
||||
|
||||
@@ -46,6 +46,10 @@ import (
|
||||
)
|
||||
|
||||
const userAnnotationOwnerKey = "user.sealos.io/creator"
|
||||
const clusterRoleByCreate = "sealos-user-create-role"
|
||||
|
||||
//const clusterRoleByManager = "sealos-user-manager-role"
|
||||
//const clusterRoleByUser = "sealos-user-user-role"
|
||||
|
||||
// UserReconciler reconciles a User object
|
||||
type UserReconciler struct {
|
||||
@@ -60,6 +64,8 @@ type UserReconciler struct {
|
||||
//+kubebuilder:rbac:groups=user.sealos.io,resources=users,verbs=get;list;watch;create;update;patch;delete
|
||||
//+kubebuilder:rbac:groups=user.sealos.io,resources=users/status,verbs=get;update;patch
|
||||
//+kubebuilder:rbac:groups=user.sealos.io,resources=users/finalizers,verbs=update
|
||||
//+kubebuilder:rbac:groups=certificates.k8s.io,resources=certificatesigningrequests,verbs=get;list;watch;create;update;patch;delete
|
||||
//+kubebuilder:rbac:groups=certificates.k8s.io,resources=certificatesigningrequests/status,verbs=get;update;patch
|
||||
|
||||
// Reconcile is part of the main kubernetes reconciliation loop which aims to
|
||||
// move the current state of the cluster closer to the desired state.
|
||||
@@ -71,7 +77,7 @@ type UserReconciler struct {
|
||||
// For more details, check Reconcile and its Result here:
|
||||
// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.12.2/pkg/reconcile
|
||||
func (r *UserReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
|
||||
r.Logger.V(4).Info("start reconcile for users")
|
||||
r.Logger.V(1).Info("start reconcile for users")
|
||||
user := &userv1.User{}
|
||||
ctr := controller.Controller{
|
||||
Client: r.Client,
|
||||
@@ -103,18 +109,19 @@ func (r *UserReconciler) SetupWithManager(mgr ctrl.Manager) error {
|
||||
r.Scheme = mgr.GetScheme()
|
||||
r.cache = mgr.GetCache()
|
||||
r.config = mgr.GetConfig()
|
||||
r.Logger.V(4).Info("init reconcile controller user")
|
||||
r.Logger.V(1).Info("init reconcile controller user")
|
||||
return ctrl.NewControllerManagedBy(mgr).
|
||||
For(&userv1.User{}).
|
||||
Complete(r)
|
||||
}
|
||||
|
||||
func (r *UserReconciler) Delete(ctx context.Context, req ctrl.Request, gvk schema.GroupVersionKind, obj client.Object) error {
|
||||
r.Logger.V(1).Info("delete reconcile controller user", "request", req)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *UserReconciler) Update(ctx context.Context, req ctrl.Request, gvk schema.GroupVersionKind, obj client.Object) (ctrl.Result, error) {
|
||||
r.Logger.V(4).Info("update reconcile controller user", "request", req)
|
||||
r.Logger.V(1).Info("update reconcile controller user", "request", req)
|
||||
user := &userv1.User{}
|
||||
err := convert.JsonConvert(obj, user)
|
||||
if err != nil {
|
||||
@@ -124,7 +131,6 @@ func (r *UserReconciler) Update(ctx context.Context, req ctrl.Request, gvk schem
|
||||
r.initStatus,
|
||||
r.syncKubeConfig,
|
||||
r.syncOwnerUG,
|
||||
r.syncOwnerUGUserBinding,
|
||||
r.syncOwnerUGNamespaceBinding,
|
||||
r.syncFinalStatus,
|
||||
}
|
||||
@@ -163,17 +169,11 @@ func (r *UserReconciler) saveCondition(user *userv1.User, condition *userv1.Cond
|
||||
user.Status.Conditions = helper.UpdateCondition(user.Status.Conditions, *condition)
|
||||
}
|
||||
}
|
||||
func (r *UserReconciler) setConditionError(condition *userv1.Condition, reason string, err error) {
|
||||
condition.LastHeartbeatTime = metav1.Now()
|
||||
condition.Status = v1.ConditionFalse
|
||||
condition.Reason = reason
|
||||
condition.Message = err.Error()
|
||||
}
|
||||
|
||||
func (r *UserReconciler) syncKubeConfig(ctx context.Context, user *userv1.User) {
|
||||
cfg := &helper.Config{
|
||||
User: user.Name,
|
||||
ExpirationSeconds: *user.Spec.CSRExpirationSeconds,
|
||||
ExpirationSeconds: user.Spec.CSRExpirationSeconds,
|
||||
}
|
||||
userConditionType := userv1.ConditionType("KubeConfigSyncReady")
|
||||
condition := &userv1.Condition{
|
||||
@@ -191,19 +191,28 @@ func (r *UserReconciler) syncKubeConfig(ctx context.Context, user *userv1.User)
|
||||
if event != nil {
|
||||
r.Recorder.Eventf(user, v1.EventTypeWarning, "syncKubeConfig", *event)
|
||||
}
|
||||
user.Status.ObservedCSRExpirationSeconds = user.Spec.CSRExpirationSeconds
|
||||
if err != nil {
|
||||
r.Recorder.Eventf(user, v1.EventTypeWarning, "syncKubeConfig", "syncReNewConfig %s is error: %v", user.Name, err)
|
||||
return
|
||||
}
|
||||
if config == nil {
|
||||
config, err = helper.NewGenerate(cfg).KubeConfig(r.config, r.Client)
|
||||
if err != nil {
|
||||
r.setConditionError(condition, "SyncKubeConfigError", err)
|
||||
helper.SetConditionError(condition, "SyncKubeConfigError", err)
|
||||
r.Recorder.Eventf(user, v1.EventTypeWarning, "syncKubeConfig", "Sync KubeConfig %s is error: %v", user.Name, err)
|
||||
return
|
||||
}
|
||||
if config == nil {
|
||||
helper.SetConditionError(condition, "SyncKubeConfigError", errors.New("api.config is nil"))
|
||||
r.Recorder.Eventf(user, v1.EventTypeWarning, "syncKubeConfig", "Sync KubeConfig %s is error: %v", user.Name, errors.New("api.config is nil"))
|
||||
return
|
||||
}
|
||||
kubeData, err := clientcmd.Write(*config)
|
||||
if err != nil {
|
||||
r.setConditionError(condition, "OutputKubeConfigError", err)
|
||||
helper.SetConditionError(condition, "OutputKubeConfigError", err)
|
||||
r.Recorder.Eventf(user, v1.EventTypeWarning, "syncKubeConfig", "Output KubeConfig %s is error: %v", user.Name, err)
|
||||
return
|
||||
}
|
||||
user.Status.KubeConfig = string(kubeData)
|
||||
}
|
||||
@@ -213,7 +222,7 @@ func syncReNewConfig(user *userv1.User) (*api.Config, *string, error) {
|
||||
var config *api.Config
|
||||
var err error
|
||||
var event *string
|
||||
if user.Status.KubeConfig != "" {
|
||||
if user.Status.KubeConfig != "" && user.Spec.CSRExpirationSeconds == user.Status.ObservedCSRExpirationSeconds {
|
||||
config, err = clientcmd.Load([]byte(user.Status.KubeConfig))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
@@ -262,60 +271,14 @@ func (r *UserReconciler) syncOwnerUG(ctx context.Context, user *userv1.User) {
|
||||
}); err != nil {
|
||||
return errors.Wrap(err, "unable to create UserGroup")
|
||||
}
|
||||
r.Logger.V(4).Info("create or update UserGroup ", "OperationResult", change)
|
||||
r.Logger.V(1).Info("create or update UserGroup ", "OperationResult", change)
|
||||
return nil
|
||||
}); err != nil {
|
||||
r.setConditionError(condition, "SyncOwnerUGError", err)
|
||||
helper.SetConditionError(condition, "SyncOwnerUGError", err)
|
||||
r.Recorder.Eventf(user, v1.EventTypeWarning, "syncOwnerUG", "Sync OwnerUG %s is error: %v", ugName, err)
|
||||
}
|
||||
}
|
||||
|
||||
func (r *UserReconciler) syncOwnerUGUserBinding(ctx context.Context, user *userv1.User) {
|
||||
userConditionType := userv1.ConditionType("OwnerUGUserBindingSyncReady")
|
||||
condition := &userv1.Condition{
|
||||
Type: userConditionType,
|
||||
Status: v1.ConditionTrue,
|
||||
LastTransitionTime: metav1.Now(),
|
||||
LastHeartbeatTime: metav1.Now(),
|
||||
Reason: string(userv1.Ready),
|
||||
Message: "sync owner ug user binding successfully",
|
||||
}
|
||||
defer r.saveCondition(user, condition)
|
||||
|
||||
uguBindingName := fmt.Sprintf("ugu-%s", user.Name)
|
||||
ugName := fmt.Sprintf("ug-%s", user.Name)
|
||||
if err := retry.RetryOnConflict(retry.DefaultRetry, func() error {
|
||||
ugBinding := &userv1.UserGroupBinding{}
|
||||
ugBinding.Name = uguBindingName
|
||||
var change controllerutil.OperationResult
|
||||
var err error
|
||||
if change, err = controllerutil.CreateOrUpdate(ctx, r.Client, ugBinding, func() error {
|
||||
if err = controllerutil.SetControllerReference(user, ugBinding, r.Scheme); err != nil {
|
||||
return err
|
||||
}
|
||||
ugBinding.Annotations = map[string]string{userAnnotationOwnerKey: user.Name}
|
||||
ugBinding.UserGroupRef = ugName
|
||||
ugBinding.Subject = rbacv1.Subject{
|
||||
Kind: "User",
|
||||
APIGroup: userv1.GroupVersion.String(),
|
||||
Name: user.Name,
|
||||
}
|
||||
ugBinding.RoleRef = &rbacv1.RoleRef{
|
||||
APIGroup: rbacv1.SchemeGroupVersion.String(),
|
||||
Kind: "ClusterRole",
|
||||
Name: "sealos-user-create-role",
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
return errors.Wrap(err, "unable to create user UserGroupBinding")
|
||||
}
|
||||
r.Logger.V(4).Info("create or update user UserGroupBinding", "OperationResult", change)
|
||||
return nil
|
||||
}); err != nil {
|
||||
r.setConditionError(condition, "SyncOwnerUGUserBindingError", err)
|
||||
r.Recorder.Eventf(user, v1.EventTypeWarning, "syncOwnerUGUserBinding", "Sync OwnerUGUserBinding %s is error: %v", uguBindingName, err)
|
||||
}
|
||||
}
|
||||
func (r *UserReconciler) syncOwnerUGNamespaceBinding(ctx context.Context, user *userv1.User) {
|
||||
userConditionType := userv1.ConditionType("OwnerUGNamespaceBindingSyncReady")
|
||||
condition := &userv1.Condition{
|
||||
@@ -350,10 +313,10 @@ func (r *UserReconciler) syncOwnerUGNamespaceBinding(ctx context.Context, user *
|
||||
}); err != nil {
|
||||
return errors.Wrap(err, "unable to create namespace UserGroupBinding")
|
||||
}
|
||||
r.Logger.V(4).Info("create or update namespace UserGroupBinding", "OperationResult", change)
|
||||
r.Logger.V(1).Info("create or update namespace UserGroupBinding", "OperationResult", change)
|
||||
return nil
|
||||
}); err != nil {
|
||||
r.setConditionError(condition, "SyncOwnerUGNamespaceBindingError", err)
|
||||
helper.SetConditionError(condition, "SyncOwnerUGNamespaceBindingError", err)
|
||||
r.Recorder.Eventf(user, v1.EventTypeWarning, "syncOwnerUGNamespaceBinding", "Sync OwnerUGNamespaceBinding %s is error: %v", ugnBindingName, err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,17 @@ package controllers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/labring/endpoints-operator/library/convert"
|
||||
"github.com/labring/sealos/controllers/user/controllers/helper"
|
||||
"github.com/pkg/errors"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
rbacv1 "k8s.io/api/rbac/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/client-go/util/retry"
|
||||
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
|
||||
|
||||
"github.com/go-logr/logr"
|
||||
"github.com/labring/endpoints-operator/library/controller"
|
||||
@@ -54,7 +65,7 @@ type UserGroupReconciler struct {
|
||||
// For more details, check Reconcile and its Result here:
|
||||
// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.12.2/pkg/reconcile
|
||||
func (r *UserGroupReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
|
||||
r.Logger.V(4).Info("start reconcile for user groups")
|
||||
r.Logger.V(1).Info("start reconcile for user groups")
|
||||
userGroup := &userv1.UserGroup{}
|
||||
ctr := controller.Controller{
|
||||
Client: r.Client,
|
||||
@@ -85,7 +96,7 @@ func (r *UserGroupReconciler) SetupWithManager(mgr ctrl.Manager) error {
|
||||
}
|
||||
r.Scheme = mgr.GetScheme()
|
||||
r.cache = mgr.GetCache()
|
||||
r.Logger.V(4).Info("init reconcile controller user group")
|
||||
r.Logger.V(1).Info("init reconcile controller user group")
|
||||
return ctrl.NewControllerManagedBy(mgr).
|
||||
For(&userv1.UserGroup{}).
|
||||
Complete(r)
|
||||
@@ -96,6 +107,136 @@ func (r *UserGroupReconciler) Delete(ctx context.Context, req ctrl.Request, gvk
|
||||
}
|
||||
|
||||
func (r *UserGroupReconciler) Update(ctx context.Context, req ctrl.Request, gvk schema.GroupVersionKind, obj client.Object) (ctrl.Result, error) {
|
||||
r.Logger.V(4).Info("update reconcile controller user group", "request", req)
|
||||
r.Logger.V(1).Info("update reconcile controller user group", "request", req)
|
||||
ug := &userv1.UserGroup{}
|
||||
err := convert.JsonConvert(obj, ug)
|
||||
if err != nil {
|
||||
return ctrl.Result{Requeue: true}, err
|
||||
}
|
||||
pipelines := []func(ctx context.Context, ug *userv1.UserGroup){
|
||||
r.initStatus,
|
||||
r.syncOwnerUGUserBinding,
|
||||
r.syncFinalStatus,
|
||||
}
|
||||
|
||||
for _, fn := range pipelines {
|
||||
fn(ctx, ug)
|
||||
}
|
||||
if ug.Status.Phase != userv1.UserUnknown {
|
||||
ug.Status.Phase = userv1.UserActive
|
||||
}
|
||||
err = r.updateStatus(ctx, req.NamespacedName, ug.Status.DeepCopy())
|
||||
if err != nil {
|
||||
r.Recorder.Eventf(ug, v1.EventTypeWarning, "SyncStatus", "Sync status %s is error: %v", ug.Name, err)
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
return ctrl.Result{}, nil
|
||||
}
|
||||
func (r *UserGroupReconciler) initStatus(ctx context.Context, ug *userv1.UserGroup) {
|
||||
var initializedCondition = userv1.Condition{
|
||||
Type: userv1.Initialized,
|
||||
Status: v1.ConditionTrue,
|
||||
Reason: string(userv1.Initialized),
|
||||
Message: "user group has been initialized",
|
||||
LastTransitionTime: metav1.Now(),
|
||||
LastHeartbeatTime: metav1.Now(),
|
||||
}
|
||||
ug.Status.Phase = userv1.UserPending
|
||||
ug.Status.ObservedGeneration = ug.Generation
|
||||
if !helper.IsConditionTrue(ug.Status.Conditions, initializedCondition) {
|
||||
ug.Status.Conditions = helper.UpdateCondition(ug.Status.Conditions, initializedCondition)
|
||||
}
|
||||
}
|
||||
func (r *UserGroupReconciler) syncFinalStatus(ctx context.Context, ug *userv1.UserGroup) {
|
||||
condition := &userv1.Condition{
|
||||
Type: userv1.Ready,
|
||||
Status: v1.ConditionTrue,
|
||||
LastTransitionTime: metav1.Now(),
|
||||
LastHeartbeatTime: metav1.Now(),
|
||||
Reason: string(userv1.Ready),
|
||||
Message: "UserGroup is available now",
|
||||
}
|
||||
defer r.saveCondition(ug, condition)
|
||||
|
||||
if !helper.IsConditionsTrue(ug.Status.Conditions) {
|
||||
condition.LastHeartbeatTime = metav1.Now()
|
||||
condition.Status = v1.ConditionFalse
|
||||
condition.Reason = "Not" + string(userv1.Ready)
|
||||
condition.Message = "UserGroup is not available now"
|
||||
ug.Status.Phase = userv1.UserUnknown
|
||||
} else {
|
||||
ug.Status.Phase = userv1.UserActive
|
||||
}
|
||||
}
|
||||
|
||||
func (r *UserGroupReconciler) updateStatus(ctx context.Context, nn types.NamespacedName, status *userv1.UserGroupStatus) error {
|
||||
if err := retry.RetryOnConflict(retry.DefaultRetry, func() error {
|
||||
original := &userv1.UserGroup{}
|
||||
if err := r.Get(ctx, nn, original); err != nil {
|
||||
return err
|
||||
}
|
||||
original.Status = *status
|
||||
if err := r.Client.Status().Update(ctx, original); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *UserGroupReconciler) saveCondition(ug *userv1.UserGroup, condition *userv1.Condition) {
|
||||
if !helper.IsConditionTrue(ug.Status.Conditions, *condition) {
|
||||
ug.Status.Conditions = helper.UpdateCondition(ug.Status.Conditions, *condition)
|
||||
}
|
||||
}
|
||||
|
||||
func (r *UserGroupReconciler) syncOwnerUGUserBinding(ctx context.Context, ug *userv1.UserGroup) {
|
||||
userConditionType := userv1.ConditionType("OwnerUGUserBindingSyncReady")
|
||||
condition := &userv1.Condition{
|
||||
Type: userConditionType,
|
||||
Status: v1.ConditionTrue,
|
||||
LastTransitionTime: metav1.Now(),
|
||||
LastHeartbeatTime: metav1.Now(),
|
||||
Reason: string(userv1.Ready),
|
||||
Message: "sync owner ug user binding successfully",
|
||||
}
|
||||
defer r.saveCondition(ug, condition)
|
||||
|
||||
userName := ug.Annotations[userAnnotationOwnerKey]
|
||||
|
||||
uguBindingName := fmt.Sprintf("ugu-%s", userName)
|
||||
ugName := fmt.Sprintf("ug-%s", userName)
|
||||
if err := retry.RetryOnConflict(retry.DefaultRetry, func() error {
|
||||
ugBinding := &userv1.UserGroupBinding{}
|
||||
ugBinding.Name = uguBindingName
|
||||
var change controllerutil.OperationResult
|
||||
var err error
|
||||
if change, err = controllerutil.CreateOrUpdate(ctx, r.Client, ugBinding, func() error {
|
||||
if err = controllerutil.SetControllerReference(ug, ugBinding, r.Scheme); err != nil {
|
||||
return err
|
||||
}
|
||||
ugBinding.Annotations = map[string]string{userAnnotationOwnerKey: userName}
|
||||
ugBinding.UserGroupRef = ugName
|
||||
ugBinding.Subject = rbacv1.Subject{
|
||||
Kind: "User",
|
||||
APIGroup: userv1.GroupVersion.String(),
|
||||
Name: userName,
|
||||
}
|
||||
ugBinding.RoleRef = &rbacv1.RoleRef{
|
||||
APIGroup: rbacv1.SchemeGroupVersion.String(),
|
||||
Kind: "ClusterRole",
|
||||
Name: clusterRoleByCreate,
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
return errors.Wrap(err, "unable to create user UserGroupBinding")
|
||||
}
|
||||
r.Logger.V(1).Info("create or update user UserGroupBinding", "OperationResult", change)
|
||||
return nil
|
||||
}); err != nil {
|
||||
helper.SetConditionError(condition, "SyncOwnerUGUserBindingError", err)
|
||||
r.Recorder.Eventf(ug, v1.EventTypeWarning, "syncOwnerUGUserBinding", "Sync OwnerUGUserBinding %s is error: %v", uguBindingName, err)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user