Skip to content

Commit

Permalink
wip: user conditions
Browse files Browse the repository at this point in the history
  • Loading branch information
katallaxie authored Dec 7, 2024
1 parent b149d67 commit 9cbfb17
Show file tree
Hide file tree
Showing 8 changed files with 273 additions and 55 deletions.
1 change: 1 addition & 0 deletions api/v1alpha1/common_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ const (
const (
ConditionReasonCreated = "Created"
ConditionReasonSynchronized = "Synchronized"
ConditionReasonFailed = "Failed"
)

const (
Expand Down
12 changes: 9 additions & 3 deletions api/v1alpha1/nats_user_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,10 +94,16 @@ type NatsUserStatus struct {
UserSecretName string `json:"userSecretName,omitempty"`
PublicKey string `json:"publicKey,omitempty"`
JWT string `json:"jwt,omitempty"`
// Phase is the current state of the user
// Conditions is an array of conditions that the operator is currently in.
Conditions []metav1.Condition `json:"conditions,omitempty" optional:"true"`
// Phase is the current phase of the operator.
//
// +kubebuilder:validation:Enum={None,Pending,Creating,Synchronized,Failed}
Phase UserPhase `json:"phase"`
// ControlerPaused is used to pause the operator for this user
ControlerPaused bool `json:"controlerPaused,omitempty"`
// ControlPaused is a flag that indicates if the operator is paused.
ControlPaused bool `json:"controlPaused,omitempty" optional:"true"`
// LastUpdate is the timestamp of the last update.
LastUpdate metav1.Time `json:"lastUpdate,omitempty"`
}

//+kubebuilder:object:root=true
Expand Down
10 changes: 9 additions & 1 deletion api/v1alpha1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 5 additions & 1 deletion controllers/natsoperator_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -396,7 +396,11 @@ func (r *NatsOperatorReconciler) ManageSuccess(ctx context.Context, obj *natsv1a
return ctrl.Result{}, nil
}

status.SetNatzOperatorCondition(obj, status.NewOperatorSynchronizingCondition(obj))
status.SetNatzOperatorCondition(obj, status.NewOperatorSychronizedCondition(obj))

if r.IsCreating(obj) {
return ctrl.Result{Requeue: true}, nil
}

if err := r.Client.Status().Update(ctx, obj); err != nil {
return ctrl.Result{}, err
Expand Down
114 changes: 74 additions & 40 deletions controllers/natsuser_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package controllers
import (
"context"
"fmt"
"math"
"time"

corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
Expand All @@ -17,9 +19,9 @@ import (
"github.com/nats-io/jwt/v2"
"github.com/nats-io/nkeys"
natsv1alpha1 "github.com/zeiss/natz-operator/api/v1alpha1"
"github.com/zeiss/pkg/cast"
"github.com/zeiss/natz-operator/pkg/status"
"github.com/zeiss/pkg/conv"
"github.com/zeiss/pkg/k8s/finalizers"
"github.com/zeiss/pkg/slices"
"github.com/zeiss/pkg/utilx"
)

Expand All @@ -36,6 +38,7 @@ const (
EventReasonUserSecretCreateSucceeded EventReason = "UserSecretCreateSucceeded"
EventReasonUserSecretCreateFailed EventReason = "UserSecretCreateFailed"
EventReasonUserSynchronizeFailed EventReason = "UserSynchronizeFailed"
EventReasonUserSynchronized EventReason = "UserSynchronized"
)

// NatsUserReconciler reconciles a NatsUser object
Expand Down Expand Up @@ -73,17 +76,7 @@ func (r *NatsUserReconciler) Reconcile(ctx context.Context, req ctrl.Request) (c
}

if !user.ObjectMeta.DeletionTimestamp.IsZero() {
log.Info("processing deletion of user")

if finalizers.HasFinalizer(user, natsv1alpha1.FinalizerName) {
err := r.reconcileDelete(ctx, user)
if err != nil {
return ctrl.Result{}, err
}
}

// Delete
return reconcile.Result{}, nil
return r.reconcileDelete(ctx, user)
}

// get latest version of the account
Expand All @@ -93,50 +86,38 @@ func (r *NatsUserReconciler) Reconcile(ctx context.Context, req ctrl.Request) (c
return reconcile.Result{}, err
}

err := r.reconcileResources(ctx, req, user)
if err != nil {
r.Recorder.Event(user, corev1.EventTypeWarning, cast.String(EventReasonUserSynchronizeFailed), "user resources reconciliation failed")
return reconcile.Result{}, err
}

return reconcile.Result{}, nil
return r.reconcileResources(ctx, req, user)
}

func (r *NatsUserReconciler) reconcileDelete(ctx context.Context, user *natsv1alpha1.NatsUser) error {
log := log.FromContext(ctx)
func (r *NatsUserReconciler) reconcileDelete(ctx context.Context, obj *natsv1alpha1.NatsUser) (ctrl.Result, error) {
// Remove our finalizer from the list.
controllerutil.RemoveFinalizer(obj, natsv1alpha1.FinalizerName)

log.Info("reconcile delete user", "name", user.Name, "namespace", user.Namespace)
if !obj.DeletionTimestamp.IsZero() {
// Remove our finalizer from the list.
controllerutil.RemoveFinalizer(obj, natsv1alpha1.FinalizerName)

user.SetFinalizers(finalizers.RemoveFinalizer(user, natsv1alpha1.FinalizerName))
err := r.Update(ctx, user)
if err != nil && !errors.IsNotFound(err) {
return err
// Stop reconciliation as the object is being deleted.
return ctrl.Result{}, r.Update(ctx, obj)
}

return nil
return ctrl.Result{Requeue: true}, nil
}

func (r *NatsUserReconciler) reconcileResources(ctx context.Context, req ctrl.Request, user *natsv1alpha1.NatsUser) error {
log := log.FromContext(ctx)

log.Info("reconcile resources", "name", user.Name, "namespace", user.Namespace)

func (r *NatsUserReconciler) reconcileResources(ctx context.Context, req ctrl.Request, user *natsv1alpha1.NatsUser) (ctrl.Result, error) {
if err := r.reconcileStatus(ctx, user); err != nil {
log.Error(err, "failed to reconcile status", "name", user.Name, "namespace", user.Namespace)
return err
return r.ManageError(ctx, user, err)
}

if err := r.reconcileUser(ctx, req, user); err != nil {
log.Error(err, "failed to reconcile user", "name", user.Name, "namespace", user.Namespace)
return err
return r.ManageError(ctx, user, err)
}

if err := r.reconcileSecret(ctx, user); err != nil {
log.Error(err, "failed to reconcile secret", "name", user.Name, "namespace", user.Namespace)
return err
return r.ManageError(ctx, user, err)
}

return nil
return r.ManageSuccess(ctx, user)
}

func (r *NatsUserReconciler) reconcileUser(ctx context.Context, req ctrl.Request, user *natsv1alpha1.NatsUser) error {
Expand Down Expand Up @@ -290,6 +271,59 @@ func (r *NatsUserReconciler) reconcileSecret(ctx context.Context, user *natsv1al
return nil
}

// IsCreating ...
func (r *NatsUserReconciler) IsCreating(obj *natsv1alpha1.NatsUser) bool {
return utilx.Or(obj.Status.Conditions == nil, slices.Len(obj.Status.Conditions) == 0)
}

// IsSynchronized ...
func (r *NatsUserReconciler) IsSynchronized(obj *natsv1alpha1.NatsUser) bool {
return obj.Status.Phase == natsv1alpha1.UserPhaseSynchronized
}

// ManageError ...
func (r *NatsUserReconciler) ManageError(ctx context.Context, obj *natsv1alpha1.NatsUser, err error) (ctrl.Result, error) {
status.SetNatzUserCondition(obj, status.NewUserFailedCondition(obj, err))

if err := r.Client.Status().Update(ctx, obj); err != nil {
return ctrl.Result{Requeue: true, RequeueAfter: time.Second}, err
}

r.Recorder.Event(obj, corev1.EventTypeWarning, conv.String(EventReasonUserSynchronizeFailed), "user synchronization failed")

var retryInterval time.Duration

return reconcile.Result{
RequeueAfter: time.Duration(math.Min(float64(retryInterval.Nanoseconds()*2), float64(time.Hour.Nanoseconds()*6))),
Requeue: true,
}, nil
}

// ManageSuccess ...
func (r *NatsUserReconciler) ManageSuccess(ctx context.Context, obj *natsv1alpha1.NatsUser) (ctrl.Result, error) {
if r.IsSynchronized(obj) {
return ctrl.Result{}, nil
}

status.SetNatzUserCondition(obj, status.NewUserSychronizedCondition(obj))

if r.IsCreating(obj) {
return ctrl.Result{Requeue: true}, nil
}

if err := r.Client.Status().Update(ctx, obj); err != nil {
return ctrl.Result{}, err
}

if !obj.ObjectMeta.DeletionTimestamp.IsZero() {
return ctrl.Result{Requeue: true}, nil
}

r.Recorder.Event(obj, corev1.EventTypeNormal, conv.String(EventReasonUserSynchronized), "user synchronized")

return ctrl.Result{}, nil
}

// SetupWithManager sets up the controller with the Manager.
func (r *NatsUserReconciler) SetupWithManager(mgr ctrl.Manager) error {
return ctrl.NewControllerManagedBy(mgr).
Expand Down
76 changes: 72 additions & 4 deletions helm/charts/natz-operator/templates/crds/natsusers.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -175,14 +175,82 @@ spec:
status:
description: NatsUserStatus defines the observed state of NatsUser
properties:
controlerPaused:
description: ControlerPaused is used to pause the operator for this
user
conditions:
description: Conditions is an array of conditions that the operator
is currently in.
items:
description: Condition contains details for one aspect of the current
state of this API Resource.
properties:
lastTransitionTime:
description: |-
lastTransitionTime is the last time the condition transitioned from one status to another.
This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.
format: date-time
type: string
message:
description: |-
message is a human readable message indicating details about the transition.
This may be an empty string.
maxLength: 32768
type: string
observedGeneration:
description: |-
observedGeneration represents the .metadata.generation that the condition was set based upon.
For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date
with respect to the current state of the instance.
format: int64
minimum: 0
type: integer
reason:
description: |-
reason contains a programmatic identifier indicating the reason for the condition's last transition.
Producers of specific condition types may define expected values and meanings for this field,
and whether the values are considered a guaranteed API.
The value should be a CamelCase string.
This field may not be empty.
maxLength: 1024
minLength: 1
pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$
type: string
status:
description: status of the condition, one of True, False, Unknown.
enum:
- "True"
- "False"
- Unknown
type: string
type:
description: type of condition in CamelCase or in foo.example.com/CamelCase.
maxLength: 316
pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$
type: string
required:
- lastTransitionTime
- message
- reason
- status
- type
type: object
type: array
controlPaused:
description: ControlPaused is a flag that indicates if the operator
is paused.
type: boolean
jwt:
type: string
lastUpdate:
description: LastUpdate is the timestamp of the last update.
format: date-time
type: string
phase:
description: Phase is the current state of the user
description: Phase is the current phase of the operator.
enum:
- None
- Pending
- Creating
- Synchronized
- Failed
type: string
publicKey:
type: string
Expand Down
Loading

0 comments on commit 9cbfb17

Please sign in to comment.