Add new properties to CRDs

This commit is contained in:
paul 2021-01-11 04:21:29 +01:00
parent 28381df56e
commit fc6f1632da
9 changed files with 538 additions and 28 deletions

View file

@ -20,22 +20,98 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
) )
// EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN!
// NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized.
// KeycloakClientSpec defines the desired state of KeycloakClient // KeycloakClientSpec defines the desired state of KeycloakClient
type KeycloakClientSpec struct { type KeycloakClientSpec struct {
// INSERT ADDITIONAL SPEC FIELDS - desired state of cluster // +kubebuilder:validation:Required
// Important: Run "make" to regenerate code after modifying this file // Name of the Realm the client should be created in
RealmName string `json:"realmName"`
// Foo is an example field of KeycloakClient. Edit KeycloakClient_types.go to remove/update // +kubebuilder:validation:Required
Foo string `json:"foo,omitempty"` // ClientID is the alphanumeric identifier of the client in a realm.
ClientID string `json:"clientId"`
// If the client is enabled and active
Enabled *bool `json:"enabled,omitempty"`
// Displayed Name of the Client
Name *string `json:"name,omitempty"`
// Human Readable description
Description *string `json:"description,omitempty"`
// Protocol, either 'openid-connect' or 'saml'
Protocol *string `json:"protocol,omitempty"`
// How should Clients authenticate to the server? either 'client-secret' or 'client-jwt'.
ClientAuthenticatorType *string `json:"clientAuthenticatorType,omitempty"`
// Are direct access grants enabled for this client or not (OpenID connect).
DirectAccessGrantsEnabled *bool `json:"directAccessGrantsEnabled,omitempty"`
// Is the access type for this client public or not.
PublicClient *bool `json:"publicClient,omitempty"`
// Enable implicit flow for this client or not (OpenID connect).
ImplicitFlowEnabled *bool `json:"implicitFlowEnabled,omitempty"`
// Enable standard flow for this client or not (OpenID connect).
StandardFlowEnabled *bool `json:"standardFlowEnabled,omitempty"`
// Are service accounts enabled for this client or not (OpenID connect).
ServiceAccountsEnabled *bool `json:"serviceAccountsEnabled,omitempty"`
// Used for authentication when registering new clients
RegistrationAccessToken *string `json:"registrationAccessToken,omitempty"`
// Whether or not surrogate auth is required.
SurrogateAuthRequired *bool `json:"surrogateAuthRequired,omitempty"`
// The access type of this client is bearer-only.
BearerOnly *bool `json:"bearerOnly,omitempty"`
// If enabled, users have to consent to client access.
ConsentRequired *bool `json:"consentRequired,omitempty"`
// Which client scopes chould be granted by default, even without
// specifying them.
DefaultClientScopes *[]string `json:"defaultClientScopes,omitempty"`
// Which additional scopes can be specified by the client
OptionalClientScopes *[]string `json:"optionalClientScopes,omitempty"`
// Default URL to use when the auth server needs to redirect or link back to the client
BaseURL *string `json:"baseUrl,omitempty"`
// Root URL appended to relative URLs for this client
RootURL *string `json:"rootUrl,omitempty"`
// URL to the admin interface of the client
AdminURL *string `json:"adminUrl,omitempty"`
// URL to the admin interface of the client
RedirectURIs *[]string `json:"redirectUris,omitempty"`
// List of allowed CORS origins
WebOrigins *[]string `json:"webOrigins,omitempty"`
// +kubebuilder:validation:Optional
// A client Secret is not always required
Secret *KeycloakClientSecret `json:"secret,omitempty"`
}
// KeycloakClientSecret contains the Secret storing the Client Secret
type KeycloakClientSecret struct {
// +kubebuilder:validation:Required
// Name of the Secret containing the client Secret.
Name string `json:"name"`
// +kubebuilder:default:=password
// Key of the attribute, that holds the value in the Secret.
Key string `json:"key,omitempty"`
} }
// KeycloakClientStatus defines the observed state of KeycloakClient // KeycloakClientStatus defines the observed state of KeycloakClient
type KeycloakClientStatus struct { type KeycloakClientStatus struct {
// INSERT ADDITIONAL STATUS FIELD - define observed state of cluster Available bool `json:"available"`
// Important: Run "make" to regenerate code after modifying this file
} }
// +kubebuilder:object:root=true // +kubebuilder:object:root=true

View file

@ -22,11 +22,14 @@ import (
// KeycloakRealmSpec defines the desired state of KeycloakRealm // KeycloakRealmSpec defines the desired state of KeycloakRealm
type KeycloakRealmSpec struct { type KeycloakRealmSpec struct {
// +kubebuilder:validation:Required
// RealmName is the name and public identifier of the Realm // RealmName is the name and public identifier of the Realm
RealmName string `json:"realmName"` RealmName string `json:"realmName"`
// Secret containing SMTP configuration // If the realm is enabled and active
SMTPSecretName string `json:"smtpSecretName,omitempty"` Enabled *bool `json:"enabled,omitempty"`
SMTP *KeycloakRealmSMTP `json:"smtp,omitempty"`
// name shown to the user // name shown to the user
DisplayName *string `json:"displayName,omitempty"` DisplayName *string `json:"displayName,omitempty"`
@ -54,10 +57,69 @@ type KeycloakRealmSpec struct {
RememberMe *bool `json:"rememberMe,omitempty"` RememberMe *bool `json:"rememberMe,omitempty"`
} }
// KeycloakRealmSMTP contains information about the SMTP server used to send
// transactional mail (for registration and password reset).
type KeycloakRealmSMTP struct {
// auth: "true"
// from: noreply@bitmask.me
// fromDisplayName: Bitmask Accounts
// host: email-smtp.eu-west-1.amazonaws.com
// password: '**********'
// port: "587"
// ssl: "false"
// starttls: "true"
// user: XXXXXXXXXXXXXXXXXXXX
// +kubebuilder:default:=true
// If authentication should be used
Auth bool `json:"auth,omitempty"`
// From which address the emails will be sent, takes precedence
// over the attribute defined in the secret.
From string `json:"from,omitempty"`
// From which NAME the email should originate.
FromDisplayName string `json:"fromDisplayName,omitempty"`
Secret *KeycloakRealmSMTPSecret `json:"secret,omitempty"`
}
// KeycloakRealmSMTPSecret contains Credentials for connecting to a SMTP
// Server.
type KeycloakRealmSMTPSecret struct {
// +kubebuilder:validation:Required
// Secret containing SMTP configuration
Name string `json:"name"`
// +kubebuilder:default:=host
// Key of the host attribute
HostKey string `json:"hostKey,omitempty"`
// +kubebuilder:default:=port
// Key of the port attribute
PortKey string `json:"portKey,omitempty"`
// +kubebuilder:default:=ssl
// Key of the ssl attribute
SSLKey string `json:"sslKey,omitempty"`
// +kubebuilder:default:=starttls
// Key of the starttls attribute
StartTLSKey string `json:"startTLSKey,omitempty"`
// +kubebuilder:default:=username
// Key of the username attribute
UsernameKey string `json:"usernameKey,omitempty"`
// +kubebuilder:default:=from
// Key of the from attribute, contains the mail address that email will be sent from.
FromKey string `json:"fromKey,omitempty"`
}
// KeycloakRealmStatus defines the observed state of KeycloakRealm // KeycloakRealmStatus defines the observed state of KeycloakRealm
type KeycloakRealmStatus struct { type KeycloakRealmStatus struct {
Available bool `json:"available"` Available bool `json:"available"`
ID string `json:"id"`
} }
// +kubebuilder:object:root=true // +kubebuilder:object:root=true

View file

@ -29,7 +29,7 @@ func (in *KeycloakClient) DeepCopyInto(out *KeycloakClient) {
*out = *in *out = *in
out.TypeMeta = in.TypeMeta out.TypeMeta = in.TypeMeta
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
out.Spec = in.Spec in.Spec.DeepCopyInto(&out.Spec)
out.Status = in.Status out.Status = in.Status
} }
@ -83,9 +83,150 @@ func (in *KeycloakClientList) DeepCopyObject() runtime.Object {
return nil return nil
} }
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *KeycloakClientSecret) DeepCopyInto(out *KeycloakClientSecret) {
*out = *in
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KeycloakClientSecret.
func (in *KeycloakClientSecret) DeepCopy() *KeycloakClientSecret {
if in == nil {
return nil
}
out := new(KeycloakClientSecret)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *KeycloakClientSpec) DeepCopyInto(out *KeycloakClientSpec) { func (in *KeycloakClientSpec) DeepCopyInto(out *KeycloakClientSpec) {
*out = *in *out = *in
if in.Enabled != nil {
in, out := &in.Enabled, &out.Enabled
*out = new(bool)
**out = **in
}
if in.Name != nil {
in, out := &in.Name, &out.Name
*out = new(string)
**out = **in
}
if in.Description != nil {
in, out := &in.Description, &out.Description
*out = new(string)
**out = **in
}
if in.Protocol != nil {
in, out := &in.Protocol, &out.Protocol
*out = new(string)
**out = **in
}
if in.ClientAuthenticatorType != nil {
in, out := &in.ClientAuthenticatorType, &out.ClientAuthenticatorType
*out = new(string)
**out = **in
}
if in.DirectAccessGrantsEnabled != nil {
in, out := &in.DirectAccessGrantsEnabled, &out.DirectAccessGrantsEnabled
*out = new(bool)
**out = **in
}
if in.PublicClient != nil {
in, out := &in.PublicClient, &out.PublicClient
*out = new(bool)
**out = **in
}
if in.ImplicitFlowEnabled != nil {
in, out := &in.ImplicitFlowEnabled, &out.ImplicitFlowEnabled
*out = new(bool)
**out = **in
}
if in.StandardFlowEnabled != nil {
in, out := &in.StandardFlowEnabled, &out.StandardFlowEnabled
*out = new(bool)
**out = **in
}
if in.ServiceAccountsEnabled != nil {
in, out := &in.ServiceAccountsEnabled, &out.ServiceAccountsEnabled
*out = new(bool)
**out = **in
}
if in.RegistrationAccessToken != nil {
in, out := &in.RegistrationAccessToken, &out.RegistrationAccessToken
*out = new(string)
**out = **in
}
if in.SurrogateAuthRequired != nil {
in, out := &in.SurrogateAuthRequired, &out.SurrogateAuthRequired
*out = new(bool)
**out = **in
}
if in.BearerOnly != nil {
in, out := &in.BearerOnly, &out.BearerOnly
*out = new(bool)
**out = **in
}
if in.ConsentRequired != nil {
in, out := &in.ConsentRequired, &out.ConsentRequired
*out = new(bool)
**out = **in
}
if in.DefaultClientScopes != nil {
in, out := &in.DefaultClientScopes, &out.DefaultClientScopes
*out = new([]string)
if **in != nil {
in, out := *in, *out
*out = make([]string, len(*in))
copy(*out, *in)
}
}
if in.OptionalClientScopes != nil {
in, out := &in.OptionalClientScopes, &out.OptionalClientScopes
*out = new([]string)
if **in != nil {
in, out := *in, *out
*out = make([]string, len(*in))
copy(*out, *in)
}
}
if in.BaseURL != nil {
in, out := &in.BaseURL, &out.BaseURL
*out = new(string)
**out = **in
}
if in.RootURL != nil {
in, out := &in.RootURL, &out.RootURL
*out = new(string)
**out = **in
}
if in.AdminURL != nil {
in, out := &in.AdminURL, &out.AdminURL
*out = new(string)
**out = **in
}
if in.RedirectURIs != nil {
in, out := &in.RedirectURIs, &out.RedirectURIs
*out = new([]string)
if **in != nil {
in, out := *in, *out
*out = make([]string, len(*in))
copy(*out, *in)
}
}
if in.WebOrigins != nil {
in, out := &in.WebOrigins, &out.WebOrigins
*out = new([]string)
if **in != nil {
in, out := *in, *out
*out = make([]string, len(*in))
copy(*out, *in)
}
}
if in.Secret != nil {
in, out := &in.Secret, &out.Secret
*out = new(KeycloakClientSecret)
**out = **in
}
} }
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KeycloakClientSpec. // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KeycloakClientSpec.
@ -172,9 +313,54 @@ func (in *KeycloakRealmList) DeepCopyObject() runtime.Object {
return nil return nil
} }
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *KeycloakRealmSMTP) DeepCopyInto(out *KeycloakRealmSMTP) {
*out = *in
if in.Secret != nil {
in, out := &in.Secret, &out.Secret
*out = new(KeycloakRealmSMTPSecret)
**out = **in
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KeycloakRealmSMTP.
func (in *KeycloakRealmSMTP) DeepCopy() *KeycloakRealmSMTP {
if in == nil {
return nil
}
out := new(KeycloakRealmSMTP)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *KeycloakRealmSMTPSecret) DeepCopyInto(out *KeycloakRealmSMTPSecret) {
*out = *in
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KeycloakRealmSMTPSecret.
func (in *KeycloakRealmSMTPSecret) DeepCopy() *KeycloakRealmSMTPSecret {
if in == nil {
return nil
}
out := new(KeycloakRealmSMTPSecret)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *KeycloakRealmSpec) DeepCopyInto(out *KeycloakRealmSpec) { func (in *KeycloakRealmSpec) DeepCopyInto(out *KeycloakRealmSpec) {
*out = *in *out = *in
if in.Enabled != nil {
in, out := &in.Enabled, &out.Enabled
*out = new(bool)
**out = **in
}
if in.SMTP != nil {
in, out := &in.SMTP, &out.SMTP
*out = new(KeycloakRealmSMTP)
(*in).DeepCopyInto(*out)
}
if in.DisplayName != nil { if in.DisplayName != nil {
in, out := &in.DisplayName, &out.DisplayName in, out := &in.DisplayName, &out.DisplayName
*out = new(string) *out = new(string)

View file

@ -36,13 +36,114 @@ spec:
spec: spec:
description: KeycloakClientSpec defines the desired state of KeycloakClient description: KeycloakClientSpec defines the desired state of KeycloakClient
properties: properties:
foo: adminUrl:
description: Foo is an example field of KeycloakClient. Edit KeycloakClient_types.go description: URL to the admin interface of the client
to remove/update
type: string type: string
baseUrl:
description: Default URL to use when the auth server needs to redirect
or link back to the client
type: string
bearerOnly:
description: The access type of this client is bearer-only.
type: boolean
clientAuthenticatorType:
description: How should Clients authenticate to the server? either
'client-secret' or 'client-jwt'.
type: string
clientId:
description: ClientID is the alphanumeric identifier of the client
in a realm.
type: string
consentRequired:
description: If enabled, users have to consent to client access.
type: boolean
defaultClientScopes:
description: Which client scopes chould be granted by default, even
without specifying them.
items:
type: string
type: array
description:
description: Human Readable description
type: string
directAccessGrantsEnabled:
description: Are direct access grants enabled for this client or not
(OpenID connect).
type: boolean
enabled:
description: If the client is enabled and active
type: boolean
implicitFlowEnabled:
description: Enable implicit flow for this client or not (OpenID connect).
type: boolean
name:
description: Displayed Name of the Client
type: string
optionalClientScopes:
description: Which additional scopes can be specified by the client
items:
type: string
type: array
protocol:
description: Protocol, either 'openid-connect' or 'saml'
type: string
publicClient:
description: Is the access type for this client public or not.
type: boolean
realmName:
description: Name of the Realm the client should be created in
type: string
redirectUris:
description: URL to the admin interface of the client
items:
type: string
type: array
registrationAccessToken:
description: Used for authentication when registering new clients
type: string
rootUrl:
description: Root URL appended to relative URLs for this client
type: string
secret:
description: A client Secret is not always required
properties:
key:
default: password
description: Key of the attribute, that holds the value in the
Secret.
type: string
name:
description: Name of the Secret containing the client Secret.
type: string
required:
- name
type: object
serviceAccountsEnabled:
description: Are service accounts enabled for this client or not (OpenID
connect).
type: boolean
standardFlowEnabled:
description: Enable standard flow for this client or not (OpenID connect).
type: boolean
surrogateAuthRequired:
description: Whether or not surrogate auth is required.
type: boolean
webOrigins:
description: List of allowed CORS origins
items:
type: string
type: array
required:
- clientId
- realmName
type: object type: object
status: status:
description: KeycloakClientStatus defines the observed state of KeycloakClient description: KeycloakClientStatus defines the observed state of KeycloakClient
properties:
available:
type: boolean
required:
- available
type: object type: object
type: object type: object
served: true served: true

View file

@ -49,6 +49,9 @@ spec:
description: if the user should be able to change their username after description: if the user should be able to change their username after
account creation account creation
type: boolean type: boolean
enabled:
description: If the realm is enabled and active
type: boolean
loginTheme: loginTheme:
description: the name of the Theme used for the login pages description: the name of the Theme used for the login pages
type: string type: string
@ -74,9 +77,58 @@ spec:
resetPasswordAllowed: resetPasswordAllowed:
description: if the user is allowed to use the reset password flow description: if the user is allowed to use the reset password flow
type: boolean type: boolean
smtpSecretName: smtp:
description: KeycloakRealmSMTP contains information about the SMTP
server used to send transactional mail (for registration and password
reset).
properties:
auth:
default: true
description: If authentication should be used
type: boolean
from:
description: From which address the emails will be sent, takes
precedence over the attribute defined in the secret.
type: string
fromDisplayName:
description: From which NAME the email should originate.
type: string
secret:
description: KeycloakRealmSMTPSecret contains Credentials for
connecting to a SMTP Server.
properties:
fromKey:
default: from
description: Key of the from attribute, contains the mail
address that email will be sent from.
type: string
hostKey:
default: host
description: Key of the host attribute
type: string
name:
description: Secret containing SMTP configuration description: Secret containing SMTP configuration
type: string type: string
portKey:
default: port
description: Key of the port attribute
type: string
sslKey:
default: ssl
description: Key of the ssl attribute
type: string
startTLSKey:
default: starttls
description: Key of the starttls attribute
type: string
usernameKey:
default: username
description: Key of the username attribute
type: string
required:
- name
type: object
type: object
verifyEmail: verifyEmail:
description: if emails should be verified before the user can log description: if emails should be verified before the user can log
into their account into their account
@ -89,11 +141,8 @@ spec:
properties: properties:
available: available:
type: boolean type: boolean
id:
type: string
required: required:
- available - available
- id
type: object type: object
type: object type: object
served: true served: true

View file

@ -6,6 +6,32 @@ metadata:
creationTimestamp: null creationTimestamp: null
name: manager-role name: manager-role
rules: rules:
- apiGroups:
- keycloak.bitmask.me
resources:
- keycloakclients
verbs:
- create
- delete
- get
- list
- patch
- update
- watch
- apiGroups:
- keycloak.bitmask.me
resources:
- keycloakclients/finalizers
verbs:
- update
- apiGroups:
- keycloak.bitmask.me
resources:
- keycloakclients/status
verbs:
- get
- patch
- update
- apiGroups: - apiGroups:
- keycloak.bitmask.me - keycloak.bitmask.me
resources: resources:

View file

@ -25,11 +25,13 @@ import (
"sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client"
keycloakv1alpha1 "git.zom.bi/images/keycloak-operator/api/v1alpha1" keycloakv1alpha1 "git.zom.bi/images/keycloak-operator/api/v1alpha1"
"git.zom.bi/images/keycloak-operator/controllers/keycloak"
) )
// KeycloakClientReconciler reconciles a KeycloakClient object // KeycloakClientReconciler reconciles a KeycloakClient object
type KeycloakClientReconciler struct { type KeycloakClientReconciler struct {
client.Client client.Client
Keycloak *keycloak.Keycloak
Log logr.Logger Log logr.Logger
Scheme *runtime.Scheme Scheme *runtime.Scheme
} }

View file

@ -63,10 +63,10 @@ func (r *KeycloakRealmReconciler) Reconcile(ctx context.Context, req ctrl.Reques
} }
} }
if realm.Status.ID != "" { if realm.Status.Available {
// try to get existing realm // try to get existing realm
log.Info("Would try to fetch the realm by its id.", log.Info("Would try to fetch the realm by its id.",
"id", realm.Status.ID) "id", realm.Spec.RealmName)
// if found { // if found {
log.Info("will act like i found it, updating.") log.Info("will act like i found it, updating.")
// update() // update()
@ -76,7 +76,6 @@ func (r *KeycloakRealmReconciler) Reconcile(ctx context.Context, req ctrl.Reques
log.Info("Would now create the realm.") log.Info("Would now create the realm.")
realm.Status.ID = "dummy"
realm.Status.Available = true realm.Status.Available = true
r.Status().Update(ctx, &realm) r.Status().Update(ctx, &realm)

View file

@ -99,6 +99,15 @@ func main() {
setupLog.Error(err, "unable to create controller", "controller", "KeycloakRealm") setupLog.Error(err, "unable to create controller", "controller", "KeycloakRealm")
os.Exit(1) os.Exit(1)
} }
if err = (&controllers.KeycloakClientReconciler{
Client: mgr.GetClient(),
Keycloak: kc,
Log: ctrl.Log.WithName("controllers").WithName("KeycloakClient"),
Scheme: mgr.GetScheme(),
}).SetupWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "KeycloakClient")
os.Exit(1)
}
// +kubebuilder:scaffold:builder // +kubebuilder:scaffold:builder
if err := mgr.AddHealthzCheck("health", healthz.Ping); err != nil { if err := mgr.AddHealthzCheck("health", healthz.Ping); err != nil {