controllers/keycloakauthflow/keycloakauthflow_controller.go (190 lines of code) (raw):

package keycloakauthflow import ( "context" "fmt" "reflect" "time" "github.com/Nerzal/gocloak/v12" "github.com/pkg/errors" k8sErrors "k8s.io/apimachinery/pkg/api/errors" 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/event" "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/pkg/client/keycloak" "github.com/epam/edp-keycloak-operator/pkg/client/keycloak/adapter" "github.com/epam/edp-keycloak-operator/pkg/objectmeta" ) const finalizerName = "keycloak.authflow.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) CreateKeycloakClientFromRealmRef(ctx context.Context, object helper.ObjectWithRealmRef) (keycloak.Client, error) SetRealmOwnerRef(ctx context.Context, object helper.ObjectWithRealmRef) error GetKeycloakRealmFromRef(ctx context.Context, object helper.ObjectWithRealmRef, kcClient keycloak.Client) (*gocloak.RealmRepresentation, error) } type Reconcile struct { client client.Client helper Helper } func NewReconcile(client client.Client, helper Helper) *Reconcile { return &Reconcile{ client: client, helper: helper, } } func (r *Reconcile) SetupWithManager(mgr ctrl.Manager) error { pred := predicate.Funcs{ UpdateFunc: isSpecUpdated, } err := ctrl.NewControllerManagedBy(mgr). For(&keycloakApi.KeycloakAuthFlow{}, builder.WithPredicates(pred)). Complete(r) if err != nil { return fmt.Errorf("failed to setup keycloakAuthFlow controller: %w", err) } return nil } func isSpecUpdated(e event.UpdateEvent) bool { oo, ok := e.ObjectOld.(*keycloakApi.KeycloakAuthFlow) if !ok { return false } no, ok := e.ObjectNew.(*keycloakApi.KeycloakAuthFlow) if !ok { return false } return !reflect.DeepEqual(oo.Spec, no.Spec) || (oo.GetDeletionTimestamp().IsZero() && !no.GetDeletionTimestamp().IsZero()) } //+kubebuilder:rbac:groups=v1.edp.epam.com,namespace=placeholder,resources=keycloakauthflows,verbs=get;list;watch;create;update;patch;delete //+kubebuilder:rbac:groups=v1.edp.epam.com,namespace=placeholder,resources=keycloakauthflows/status,verbs=get;update;patch //+kubebuilder:rbac:groups=v1.edp.epam.com,namespace=placeholder,resources=keycloakauthflows/finalizers,verbs=update // Reconcile is a loop for reconciling KeycloakAuthFlow object. func (r *Reconcile) Reconcile(ctx context.Context, request reconcile.Request) (reconcile.Result, error) { log := ctrl.LoggerFrom(ctx) log.Info("Reconciling KeycloakAuthFlow") authFlow := &keycloakApi.KeycloakAuthFlow{} if err := r.client.Get(ctx, request.NamespacedName, authFlow); err != nil { if k8sErrors.IsNotFound(err) { return reconcile.Result{}, nil } return reconcile.Result{}, fmt.Errorf("unable to get keycloak auth flow from k8s: %w", err) } if updated, err := r.applyDefaults(ctx, authFlow); err != nil { return reconcile.Result{}, err } else if updated { return reconcile.Result{}, nil } oldStatus := authFlow.Status if err := r.tryReconcile(ctx, authFlow); err != nil { if errors.Is(err, helper.ErrKeycloakIsNotAvailable) { return helper.RequeueOnKeycloakNotAvailable, nil } authFlow.Status.Value = err.Error() if statusErr := r.updateKeycloakAuthFlowStatus(ctx, authFlow, oldStatus); statusErr != nil { return ctrl.Result{}, err } return ctrl.Result{}, err } authFlow.Status.Value = helper.StatusOK if statusErr := r.updateKeycloakAuthFlowStatus(ctx, authFlow, oldStatus); statusErr != nil { return ctrl.Result{}, statusErr } log.Info("Reconciling KeycloakAuthFlow done.") return ctrl.Result{}, nil } func (r *Reconcile) tryReconcile(ctx context.Context, instance *keycloakApi.KeycloakAuthFlow) error { if err := r.helper.SetRealmOwnerRef(ctx, instance); err != nil { return fmt.Errorf("unable to set realm owner ref: %w", err) } kClient, err := r.helper.CreateKeycloakClientFromRealmRef(ctx, instance) if err != nil { return fmt.Errorf("unable to create keycloak client from realm ref: %w", err) } realm, err := r.helper.GetKeycloakRealmFromRef(ctx, instance, kClient) if err != nil { return fmt.Errorf("unable to get realm from ref: %w", err) } keycloakAuthFlow := authFlowSpecToAdapterAuthFlow(&instance.Spec) deleted, err := r.helper.TryToDelete( ctx, instance, makeTerminator( gocloak.PString(realm.Realm), instance.GetRealmRef().Name, keycloakAuthFlow, r.client, kClient, objectmeta.PreserveResourcesOnDeletion(instance), ), finalizerName, ) if err != nil { return fmt.Errorf("unable to delete auth flow: %w", err) } if deleted { return nil } if err = kClient.SyncAuthFlow(gocloak.PString(realm.Realm), keycloakAuthFlow); err != nil { return fmt.Errorf("unable to sync auth flow: %w", err) } return nil } func (r *Reconcile) applyDefaults(ctx context.Context, instance *keycloakApi.KeycloakAuthFlow) (bool, error) { if instance.Spec.RealmRef.Name == "" { instance.Spec.RealmRef = common.RealmRef{ Kind: keycloakApi.KeycloakRealmKind, Name: instance.Spec.Realm, } 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 } func authFlowSpecToAdapterAuthFlow(spec *keycloakApi.KeycloakAuthFlowSpec) *adapter.KeycloakAuthFlow { flow := adapter.KeycloakAuthFlow{ Alias: spec.Alias, Description: spec.Description, BuiltIn: spec.BuiltIn, ProviderID: spec.ProviderID, TopLevel: spec.TopLevel, AuthenticationExecutions: make([]adapter.AuthenticationExecution, 0, len(spec.AuthenticationExecutions)), ParentName: spec.ParentName, ChildType: spec.ChildType, ChildRequirement: spec.ChildRequirement, } for _, ae := range spec.AuthenticationExecutions { exec := adapter.AuthenticationExecution{ Authenticator: ae.Authenticator, Requirement: ae.Requirement, Priority: ae.Priority, AutheticatorFlow: ae.AuthenticatorFlow, Alias: ae.Alias, } if ae.AuthenticatorConfig != nil { exec.AuthenticatorConfig = &adapter.AuthenticatorConfig{ Alias: ae.AuthenticatorConfig.Alias, Config: ae.AuthenticatorConfig.Config, } } flow.AuthenticationExecutions = append(flow.AuthenticationExecutions, exec) } return &flow } func (r *Reconcile) updateKeycloakAuthFlowStatus( ctx context.Context, authFlow *keycloakApi.KeycloakAuthFlow, oldStatus keycloakApi.KeycloakAuthFlowStatus, ) error { if authFlow.Status == oldStatus { return nil } if err := r.client.Status().Update(ctx, authFlow); err != nil { return fmt.Errorf("failed to update KeycloakAuthFlow status: %w", err) } return nil }