controllers/helper/controller_helper.go (237 lines of code) (raw):
package helper
import (
"context"
"fmt"
"sync"
"time"
"github.com/Nerzal/gocloak/v12"
"github.com/go-logr/logr"
"github.com/go-resty/resty/v2"
"github.com/pkg/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
"github.com/epam/edp-keycloak-operator/api/common"
keycloakApi "github.com/epam/edp-keycloak-operator/api/v1"
keycloakAlpha "github.com/epam/edp-keycloak-operator/api/v1alpha1"
"github.com/epam/edp-keycloak-operator/pkg/client/keycloak"
"github.com/epam/edp-keycloak-operator/pkg/client/keycloak/adapter"
)
const (
StatusOK = "OK"
RequeueOnKeycloakNotAvailablePeriod = time.Minute
)
var (
RequeueOnKeycloakNotAvailable = ctrl.Result{
RequeueAfter: RequeueOnKeycloakNotAvailablePeriod,
}
)
type Terminator interface {
DeleteResource(ctx context.Context) error
}
type ObjectWithRealmRef interface {
common.HasRealmRef
client.Object
}
type ObjectWithKeycloakRef interface {
common.HasKeycloakRef
client.Object
}
type adapterBuilder func(
ctx context.Context,
conf adapter.GoCloakConfig,
adminType string,
log logr.Logger,
restyClient *resty.Client,
) (keycloak.Client, error)
// ControllerHelper interface defines methods for working with keycloak client and owner references.
//
//go:generate mockery --name ControllerHelper --filename helper_mock.go
type ControllerHelper interface {
SetKeycloakOwnerRef(ctx context.Context, object ObjectWithKeycloakRef) error
SetRealmOwnerRef(ctx context.Context, object ObjectWithRealmRef) error
SetFailureCount(fc FailureCountable) time.Duration
TryToDelete(ctx context.Context, obj client.Object, terminator Terminator, finalizer string) (isDeleted bool, resultErr error)
GetKeycloakRealmFromRef(ctx context.Context, object ObjectWithRealmRef, kcClient keycloak.Client) (*gocloak.RealmRepresentation, error)
CreateKeycloakClientFromRealmRef(ctx context.Context, object ObjectWithRealmRef) (keycloak.Client, error)
CreateKeycloakClientFromRealm(ctx context.Context, realm *keycloakApi.KeycloakRealm) (keycloak.Client, error)
CreateKeycloakClientFromClusterRealm(ctx context.Context, realm *keycloakAlpha.ClusterKeycloakRealm) (keycloak.Client, error)
CreateKeycloakClient(ctx context.Context, url, user, password, adminType, caCert string, insecureSkipVerify bool) (keycloak.Client, error)
CreateKeycloakClientFomAuthData(ctx context.Context, authData *KeycloakAuthData) (keycloak.Client, error)
InvalidateKeycloakClientTokenSecret(ctx context.Context, namespace, rootKeycloakName string) error
}
type Helper struct {
client client.Client
scheme *runtime.Scheme
restyClient *resty.Client
adapterBuilder adapterBuilder
tokenSecretLock *sync.Mutex
operatorNamespace string
}
func MakeHelper(client client.Client, scheme *runtime.Scheme, operatorNamespace string) *Helper {
return &Helper{
tokenSecretLock: new(sync.Mutex),
client: client,
scheme: scheme,
operatorNamespace: operatorNamespace,
adapterBuilder: func(
ctx context.Context,
conf adapter.GoCloakConfig,
adminType string,
log logr.Logger,
restyClient *resty.Client,
) (keycloak.Client, error) {
if adminType == keycloakApi.KeycloakAdminTypeServiceAccount {
goKeycloakAdapter, err := adapter.MakeFromServiceAccount(ctx, conf, "master", log, restyClient)
if err != nil {
return nil, fmt.Errorf("failed to make go keycloak adapter from seviceaccount: %w", err)
}
return goKeycloakAdapter, nil
}
goKeycloakAdapter, err := adapter.Make(ctx, conf, log, restyClient)
if err != nil {
return nil, fmt.Errorf("failed to make go keycloak adapter: %w", err)
}
return goKeycloakAdapter, nil
},
}
}
// SetKeycloakOwnerRef sets owner reference for object.
//
//nolint:dupl,cyclop
func (h *Helper) SetKeycloakOwnerRef(ctx context.Context, object ObjectWithKeycloakRef) error {
if metav1.GetControllerOf(object) != nil {
return nil
}
kind := object.GetKeycloakRef().Kind
name := object.GetKeycloakRef().Name
switch kind {
case keycloakApi.KeycloakKind:
kc := &keycloakApi.Keycloak{}
if err := h.client.Get(ctx, types.NamespacedName{
Namespace: object.GetNamespace(),
Name: name,
}, kc); err != nil {
return fmt.Errorf("failed to get Keycloak: %w", err)
}
if err := controllerutil.SetControllerReference(kc, object, h.scheme); err != nil {
return fmt.Errorf("failed to set controller reference for %s: %w", object.GetName(), err)
}
if err := h.client.Update(ctx, object); err != nil {
return fmt.Errorf("failed to update keycloak owner reference %s: %w", kc.GetName(), err)
}
return nil
case keycloakAlpha.ClusterKeycloakKind:
clusterKc := &keycloakAlpha.ClusterKeycloak{}
if err := h.client.Get(ctx, types.NamespacedName{
Name: name,
}, clusterKc); err != nil {
return fmt.Errorf("failed to get ClusterKeycloak: %w", err)
}
if err := controllerutil.SetControllerReference(clusterKc, object, h.scheme); err != nil {
return fmt.Errorf("failed to set controller reference for %s: %w", object.GetName(), err)
}
if err := h.client.Update(ctx, object); err != nil {
return fmt.Errorf("failed to update keycloak owner reference %s: %w", clusterKc.GetName(), err)
}
return nil
default:
return fmt.Errorf("unknown keycloak kind: %s", kind)
}
}
// SetRealmOwnerRef sets owner reference for object.
//
//nolint:dupl,cyclop
func (h *Helper) SetRealmOwnerRef(ctx context.Context, object ObjectWithRealmRef) error {
if metav1.GetControllerOf(object) != nil {
return nil
}
kind := object.GetRealmRef().Kind
name := object.GetRealmRef().Name
switch kind {
case keycloakApi.KeycloakRealmKind:
realm := &keycloakApi.KeycloakRealm{}
if err := h.client.Get(ctx, types.NamespacedName{
Namespace: object.GetNamespace(),
Name: name,
}, realm); err != nil {
return fmt.Errorf("failed to get KeycloakRealm: %w", err)
}
if err := controllerutil.SetControllerReference(realm, object, h.scheme); err != nil {
return fmt.Errorf("failed to set controller reference for %s: %w", object.GetName(), err)
}
if err := h.client.Update(ctx, object); err != nil {
return fmt.Errorf("failed to update realm owner reference %s: %w", realm.GetName(), err)
}
return nil
case keycloakAlpha.ClusterKeycloakRealmKind:
clusterRealm := &keycloakAlpha.ClusterKeycloakRealm{}
if err := h.client.Get(ctx, types.NamespacedName{
Name: name,
}, clusterRealm); err != nil {
return fmt.Errorf("failed to get ClusterKeycloakRealm: %w", err)
}
if err := controllerutil.SetControllerReference(clusterRealm, object, h.scheme); err != nil {
return fmt.Errorf("unable to set controller reference for %s: %w", object.GetName(), err)
}
if err := h.client.Update(ctx, object); err != nil {
return fmt.Errorf("failed to update realm owner reference %s: %w", clusterRealm.GetName(), err)
}
return nil
default:
return fmt.Errorf("unknown realm kind: %s", kind)
}
}
func (h *Helper) TryToDelete(ctx context.Context, obj client.Object, terminator Terminator, finalizer string) (isDeleted bool, resultErr error) {
logger := ctrl.LoggerFrom(ctx)
if obj.GetDeletionTimestamp().IsZero() {
logger.Info("instance timestamp is zero")
if controllerutil.AddFinalizer(obj, finalizer) {
logger.Info("Adding finalizer to instance")
if err := h.client.Update(ctx, obj); err != nil {
return false, errors.Wrap(err, "unable to update deletable object")
}
}
logger.Info("processing finalizers done, exit.")
return false, nil
}
logger.Info("terminator deleting resource")
if err := terminator.DeleteResource(ctx); err != nil {
return false, errors.Wrap(err, "error during keycloak resource deletion")
}
logger.Info("terminator removing finalizers")
if controllerutil.RemoveFinalizer(obj, finalizer) {
if err := h.client.Update(ctx, obj); err != nil {
return false, errors.Wrap(err, "unable to update instance")
}
}
logger.Info("terminator deleting instance done, exit")
return true, nil
}
func (h *Helper) GetKeycloakRealmFromRef(ctx context.Context, object ObjectWithRealmRef, kcClient keycloak.Client) (*gocloak.RealmRepresentation, error) {
kind := object.GetRealmRef().Kind
name := object.GetRealmRef().Name
switch kind {
case keycloakApi.KeycloakRealmKind:
realm := &keycloakApi.KeycloakRealm{}
if err := h.client.Get(ctx, types.NamespacedName{
Namespace: object.GetNamespace(),
Name: name,
}, realm); err != nil {
return nil, fmt.Errorf("failed to get KeycloakRealm: %w", err)
}
kcRealm, err := kcClient.GetRealm(ctx, realm.Spec.RealmName)
if err != nil {
return nil, fmt.Errorf("failed to get realm: %w", err)
}
return kcRealm, nil
case keycloakAlpha.ClusterKeycloakRealmKind:
clusterRealm := &keycloakAlpha.ClusterKeycloakRealm{}
if err := h.client.Get(ctx, types.NamespacedName{
Name: name,
}, clusterRealm); err != nil {
return nil, fmt.Errorf("failed to get ClusterKeycloakRealm: %w", err)
}
kcRealm, err := kcClient.GetRealm(ctx, clusterRealm.Spec.RealmName)
if err != nil {
return nil, fmt.Errorf("failed to get realm: %w", err)
}
return kcRealm, nil
default:
return nil, fmt.Errorf("unknown realm kind: %s", kind)
}
}