controllers/keycloakrealm/keycloakrealm_controller.go (131 lines of code) (raw):
package keycloakrealm
import (
"context"
"fmt"
"time"
"github.com/pkg/errors"
k8sErrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/runtime"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/builder"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/predicate"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
"github.com/epam/edp-keycloak-operator/api/common"
keycloakApi "github.com/epam/edp-keycloak-operator/api/v1"
"github.com/epam/edp-keycloak-operator/controllers/helper"
"github.com/epam/edp-keycloak-operator/controllers/keycloakrealm/chain"
"github.com/epam/edp-keycloak-operator/controllers/keycloakrealm/chain/handler"
"github.com/epam/edp-keycloak-operator/pkg/client/keycloak"
"github.com/epam/edp-keycloak-operator/pkg/objectmeta"
)
const keyCloakRealmOperatorFinalizerName = "keycloak.realm.operator.finalizer.name"
type Helper interface {
SetFailureCount(fc helper.FailureCountable) time.Duration
TryToDelete(ctx context.Context, obj client.Object, terminator helper.Terminator, finalizer string) (isDeleted bool, resultErr error)
CreateKeycloakClientFromRealm(ctx context.Context, realm *keycloakApi.KeycloakRealm) (keycloak.Client, error)
SetKeycloakOwnerRef(ctx context.Context, object helper.ObjectWithKeycloakRef) error
InvalidateKeycloakClientTokenSecret(ctx context.Context, namespace, rootKeycloakName string) error
}
func NewReconcileKeycloakRealm(client client.Client, scheme *runtime.Scheme, helper Helper) *ReconcileKeycloakRealm {
return &ReconcileKeycloakRealm{
client: client,
helper: helper,
chain: chain.CreateDefChain(client, scheme, helper),
}
}
// ReconcileKeycloakRealm reconciles a KeycloakRealm object.
type ReconcileKeycloakRealm struct {
client client.Client
helper Helper
chain handler.RealmHandler
successReconcileTimeout time.Duration
}
func (r *ReconcileKeycloakRealm) SetupWithManager(mgr ctrl.Manager, successReconcileTimeout time.Duration) error {
r.successReconcileTimeout = successReconcileTimeout
pred := predicate.Funcs{
UpdateFunc: helper.IsFailuresUpdated,
}
err := ctrl.NewControllerManagedBy(mgr).
For(&keycloakApi.KeycloakRealm{}, builder.WithPredicates(pred)).
Complete(r)
if err != nil {
return fmt.Errorf("failed to setup KeycloakRealm controller: %w", err)
}
return nil
}
//+kubebuilder:rbac:groups=v1.edp.epam.com,namespace=placeholder,resources=keycloakrealms,verbs=get;list;watch;create;update;patch;delete
//+kubebuilder:rbac:groups=v1.edp.epam.com,namespace=placeholder,resources=keycloakrealms/status,verbs=get;update;patch
//+kubebuilder:rbac:groups=v1.edp.epam.com,namespace=placeholder,resources=keycloakrealms/finalizers,verbs=update
//+kubebuilder:rbac:groups="",namespace=placeholder,resources=secrets,verbs=get;list;watch;create;update;patch;delete
// Reconcile is a loop for reconciling KeycloakRealm object.
func (r *ReconcileKeycloakRealm) Reconcile(ctx context.Context, request reconcile.Request) (result reconcile.Result, resultErr error) {
log := ctrl.LoggerFrom(ctx)
log.Info("Reconciling KeycloakRealm")
instance := &keycloakApi.KeycloakRealm{}
if err := r.client.Get(ctx, request.NamespacedName, instance); err != nil {
if k8sErrors.IsNotFound(err) {
// Request object not found, could have been deleted after reconcile request.
// Owned objects are automatically garbage collected. For additional cleanup logic use finalizers.
// Return and don't requeue
return
}
resultErr = err
return
}
if updated, err := r.applyDefaults(ctx, instance); err != nil {
resultErr = fmt.Errorf("unable to apply default values: %w", err)
return
} else if updated {
return
}
if err := r.tryReconcile(ctx, instance); err != nil {
if errors.Is(err, helper.ErrKeycloakIsNotAvailable) {
return ctrl.Result{
RequeueAfter: helper.RequeueOnKeycloakNotAvailablePeriod,
}, nil
}
instance.Status.Available = false
instance.Status.Value = err.Error()
result.RequeueAfter = r.helper.SetFailureCount(instance)
log.Error(err, "an error has occurred while handling keycloak realm", "name", request.Name)
} else {
instance.Status.Available = true
instance.Status.Value = helper.StatusOK
instance.Status.FailureCount = 0
result.RequeueAfter = r.successReconcileTimeout
}
if err := r.client.Status().Update(ctx, instance); err != nil {
resultErr = errors.Wrap(err, "unable to update status")
}
return
}
func (r *ReconcileKeycloakRealm) tryReconcile(ctx context.Context, realm *keycloakApi.KeycloakRealm) error {
if err := r.helper.SetKeycloakOwnerRef(ctx, realm); err != nil {
return fmt.Errorf("failed to set keycloak owner reference: %w", err)
}
kClient, err := r.helper.CreateKeycloakClientFromRealm(ctx, realm)
if err != nil {
return fmt.Errorf("failed to create keycloak client for realm: %w", err)
}
deleted, err := r.helper.TryToDelete(
ctx,
realm,
makeTerminator(realm.Spec.RealmName, kClient, objectmeta.PreserveResourcesOnDeletion(realm)),
keyCloakRealmOperatorFinalizerName,
)
if err != nil {
return fmt.Errorf("failed to delete realm: %w", err)
}
if deleted {
return nil
}
if err := r.chain.ServeRequest(ctx, realm, kClient); err != nil {
return errors.Wrap(err, "error during realm chain")
}
return nil
}
func (r *ReconcileKeycloakRealm) applyDefaults(ctx context.Context, instance *keycloakApi.KeycloakRealm) (bool, error) {
if instance.Spec.KeycloakRef.Name == "" {
instance.Spec.KeycloakRef = common.KeycloakRef{
Kind: keycloakApi.KeycloakKind,
Name: instance.Spec.KeycloakOwner,
}
if err := r.client.Update(ctx, instance); err != nil {
return false, fmt.Errorf("failed to update default values: %w", err)
}
return true, nil
}
return false, nil
}