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:
cuisongliu
2022-08-23 20:32:46 +08:00
committed by GitHub
parent a7347586a6
commit b99f216412
12 changed files with 343 additions and 95 deletions
+5 -1
View File
@@ -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
+20
View File
@@ -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
+40 -18
View File
@@ -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()
}
+2 -2
View File
@@ -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)
+27 -64
View File
@@ -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)
}
}