Skip to content

Commit af17494

Browse files
committed
feat: Introduce KeycloakOrganization feature (#115)
- Added KeycloakOrganization custom resource definition (CRD) to support multi-tenant scenarios. - Implemented OrganizationsEnabled field in KeycloakRealm and ClusterKeycloakRealm to enable organization features. - Implemented Identity provider configuration for KeycloakOrganization. - Moved StatusOK from internal/controller/helper to api/common for consistency. - Added general operator finalizer common.FinalizerName. Please note: Organizations feature is available in Keycloak 25 as feature flag. Starting with Keycloak 26, the Organizations feature is fully supported.
1 parent 51cfce9 commit af17494

File tree

68 files changed

+4740
-407
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

68 files changed

+4740
-407
lines changed

PROJECT

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,4 +126,13 @@ resources:
126126
kind: ClusterKeycloakRealm
127127
path: github.com/epam/edp-keycloak-operator/api/v1alpha1
128128
version: v1alpha1
129+
- api:
130+
crdVersion: v1
131+
namespaced: true
132+
controller: true
133+
domain: edp.epam.com
134+
group: v1
135+
kind: KeycloakOrganization
136+
path: github.com/epam/edp-keycloak-operator/api/v1alpha1
137+
version: v1alpha1
129138
version: "3"

api/common/status.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package common
2+
3+
const (
4+
StatusOK string = "OK"
5+
StatusError string = "error"
6+
FinalizerName string = "v1.edp.epam.com/finalizer"
7+
)

api/v1/keycloakrealm_types.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,13 @@ type KeycloakRealmSpec struct {
6767
// +optional
6868
DisplayName string `json:"displayName,omitempty"`
6969

70+
// OrganizationsEnabled enables Keycloak Organizations feature for this realm.
71+
// When enabled, this realm can support Organization resources for multi-tenant scenarios,
72+
// identity provider groupings, and domain-based user routing.
73+
// +optional
74+
// +kubebuilder:default=false
75+
OrganizationsEnabled bool `json:"organizationsEnabled,omitempty"`
76+
7077
// UserProfileConfig is the configuration for user profiles in the realm.
7178
// Attributes and groups will be added to the current realm configuration.
7279
// Deletion of attributes and groups is not supported.

api/v1alpha1/clusterkeycloakrealm_types.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,13 @@ type ClusterKeycloakRealmSpec struct {
6363
// +optional
6464
DisplayName string `json:"displayName,omitempty"`
6565

66+
// OrganizationsEnabled enables Keycloak Organizations feature for this realm.
67+
// When enabled, this realm can support Organization resources for multi-tenant scenarios,
68+
// identity provider groupings, and domain-based user routing.
69+
// +optional
70+
// +kubebuilder:default=false
71+
OrganizationsEnabled bool `json:"organizationsEnabled,omitempty"`
72+
6673
// UserProfileConfig is the configuration for user profiles in the realm.
6774
// +nullable
6875
// +optional

api/v1alpha1/organization_types.go

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
package v1alpha1
2+
3+
import (
4+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
5+
6+
"github.com/epam/edp-keycloak-operator/api/common"
7+
)
8+
9+
// KeycloakOrganizationSpec defines the desired state of Organization.
10+
type KeycloakOrganizationSpec struct {
11+
// Name is the unique name of the organization.
12+
// The name should be unique across Organizations.
13+
// +required
14+
Name string `json:"name"`
15+
16+
// Alias is the unique alias for the organization.
17+
// The alias should be unique across Organizations.
18+
// +required
19+
Alias string `json:"alias"`
20+
21+
// Domains is a list of email domains associated with the organization.
22+
// Each domain should be unique across Organizations.
23+
// +required
24+
// +kubebuilder:validation:MinItems=1
25+
Domains []string `json:"domains"`
26+
27+
// RedirectURL is the optional redirect URL for the organization.
28+
// +optional
29+
RedirectURL string `json:"redirectUrl,omitempty"`
30+
31+
// Description is an optional description of the organization.
32+
// +optional
33+
Description string `json:"description,omitempty"`
34+
35+
// Attributes is a map of custom attributes for the organization.
36+
// +optional
37+
// +nullable
38+
Attributes map[string][]string `json:"attributes,omitempty"`
39+
40+
// IdentityProviders is a list of identity providers associated with the organization.
41+
// One identity provider can't be assigned to multiple organizations.
42+
// +optional
43+
// +nullable
44+
IdentityProviders []OrgIdentityProvider `json:"identityProviders,omitempty"`
45+
46+
// RealmRef is reference to Realm custom resource.
47+
// +required
48+
RealmRef common.RealmRef `json:"realmRef"`
49+
}
50+
51+
// OrgIdentityProvider defines an identity provider for an organization.
52+
type OrgIdentityProvider struct {
53+
// Alias is the unique identifier for the identity provider within the organization.
54+
// +required
55+
Alias string `json:"alias"`
56+
}
57+
58+
// KeycloakOrganizationStatus defines the observed state of Organization.
59+
type KeycloakOrganizationStatus struct {
60+
// Value contains the current reconciliation status.
61+
// +optional
62+
Value string `json:"value,omitempty"`
63+
64+
// OrganizationID is the unique identifier of the organization in Keycloak.
65+
// +optional
66+
OrganizationID string `json:"organizationId,omitempty"`
67+
68+
// Error is the error message if the reconciliation failed.
69+
// +optional
70+
Error string `json:"error,omitempty"`
71+
}
72+
73+
func (in *KeycloakOrganizationStatus) SetOK() {
74+
in.Value = common.StatusOK
75+
in.Error = ""
76+
}
77+
78+
func (in *KeycloakOrganizationStatus) SetError(err string) {
79+
in.Value = common.StatusError
80+
in.Error = err
81+
}
82+
83+
// +kubebuilder:object:root=true
84+
// +kubebuilder:subresource:status
85+
// +kubebuilder:storageversion
86+
// +kubebuilder:printcolumn:name="Status",type="string",JSONPath=".status.value",description="Reconciliation status"
87+
// +kubebuilder:printcolumn:name="Organization ID",type="string",JSONPath=".status.organizationId",description="Keycloak organization ID"
88+
// +kubebuilder:printcolumn:name="Realm",type="string",JSONPath=".spec.realmName",description="Keycloak realm name"
89+
// +kubebuilder:printcolumn:name="Keycloak",type="string",JSONPath=".spec.keycloakRef.name",description="Keycloak instance name"
90+
91+
// KeycloakOrganization is the Schema for the organizations API.
92+
type KeycloakOrganization struct {
93+
metav1.TypeMeta `json:",inline"`
94+
metav1.ObjectMeta `json:"metadata,omitempty"`
95+
96+
Spec KeycloakOrganizationSpec `json:"spec,omitempty"`
97+
Status KeycloakOrganizationStatus `json:"status,omitempty"`
98+
}
99+
100+
func (in *KeycloakOrganization) GetRealmRef() common.RealmRef {
101+
return in.Spec.RealmRef
102+
}
103+
104+
// +kubebuilder:object:root=true
105+
106+
// KeycloakOrganizationList contains a list of KeycloakOrganization.
107+
type KeycloakOrganizationList struct {
108+
metav1.TypeMeta `json:",inline"`
109+
metav1.ListMeta `json:"metadata,omitempty"`
110+
Items []KeycloakOrganization `json:"items"`
111+
}
112+
113+
func init() {
114+
SchemeBuilder.Register(&KeycloakOrganization{}, &KeycloakOrganizationList{})
115+
}

api/v1alpha1/zz_generated.deepcopy.go

Lines changed: 131 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

cmd/main.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ import (
3636
"github.com/epam/edp-keycloak-operator/internal/controller/keycloakauthflow"
3737
"github.com/epam/edp-keycloak-operator/internal/controller/keycloakclient"
3838
"github.com/epam/edp-keycloak-operator/internal/controller/keycloakclientscope"
39+
"github.com/epam/edp-keycloak-operator/internal/controller/keycloakorganization"
3940
"github.com/epam/edp-keycloak-operator/internal/controller/keycloakrealm"
4041
"github.com/epam/edp-keycloak-operator/internal/controller/keycloakrealmcomponent"
4142
"github.com/epam/edp-keycloak-operator/internal/controller/keycloakrealmgroup"
@@ -267,6 +268,11 @@ func main() {
267268
}
268269
}
269270

271+
organizationCtrl := keycloakorganization.NewReconcileOrganization(mgr.GetClient(), h)
272+
if err = organizationCtrl.SetupWithManager(mgr); err != nil {
273+
setupLog.Error(err, "unable to create keycloak-organization controller")
274+
os.Exit(1)
275+
}
270276
//+kubebuilder:scaffold:builder
271277

272278
if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil {

config/crd/bases/v1.edp.epam.com_clusterkeycloakrealms.yaml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,13 @@ spec:
9797
nullable: true
9898
type: boolean
9999
type: object
100+
organizationsEnabled:
101+
default: false
102+
description: |-
103+
OrganizationsEnabled enables Keycloak Organizations feature for this realm.
104+
When enabled, this realm can support Organization resources for multi-tenant scenarios,
105+
identity provider groupings, and domain-based user routing.
106+
type: boolean
100107
passwordPolicy:
101108
description: PasswordPolicies is a list of password policies to apply
102109
to the realm.

0 commit comments

Comments
 (0)