From bcaf4f1ac897bbfe6d589a06ab3d3518cb6b2dbb Mon Sep 17 00:00:00 2001 From: Sebastian Laskawiec Date: Tue, 20 Oct 2020 12:33:50 +0200 Subject: [PATCH] KEYCLOAK-14782 KEYCLOAK-14470 KEYCLOAK-12677 Custom Configuration --- deploy/crds/keycloak.org_keycloaks_crd.yaml | 183 ++++++++++++++++++ .../keycloak-with-experimental-settings.yaml | 31 +++ go.sum | 1 + pkg/apis/keycloak/v1alpha1/keycloak_types.go | 69 ++++++- .../v1alpha1/zz_generated.deepcopy.go | 152 ++++++++++++++- .../keycloak/v1alpha1/zz_generated.openapi.go | 96 ++++----- .../keycloak/keycloak_reconciler_test.go | 20 +- pkg/model/keycloak_deployment.go | 83 +++++++- pkg/model/keycloak_deployment_test.go | 157 +++++++++++++++ pkg/model/rhsso_deployment.go | 21 +- pkg/model/rhsso_deployment_test.go | 21 ++ pkg/model/util.go | 18 ++ pkg/model/util_test.go | 67 +++++++ 13 files changed, 834 insertions(+), 85 deletions(-) create mode 100644 deploy/examples/keycloak/keycloak-with-experimental-settings.yaml create mode 100644 pkg/model/keycloak_deployment_test.go create mode 100644 pkg/model/rhsso_deployment_test.go diff --git a/deploy/crds/keycloak.org_keycloaks_crd.yaml b/deploy/crds/keycloak.org_keycloaks_crd.yaml index cb122dc6e..342509c5c 100644 --- a/deploy/crds/keycloak.org_keycloaks_crd.yaml +++ b/deploy/crds/keycloak.org_keycloaks_crd.yaml @@ -99,6 +99,189 @@ spec: keycloakDeploymentSpec: description: Resources (Requests and Limits) for KeycloakDeployment. properties: + experimental: + description: 'Experimental section NOTE: This section might change + or get removed without any notice. It may also cause the deployment + to behave in an unpredictable fashion. Please use with care.' + properties: + args: + description: Arguments to the entrypoint. Translates into Container + CMD. + items: + type: string + type: array + command: + description: Container command. Translates into Container ENTRYPOINT. + items: + type: string + type: array + env: + description: List of environment variables to set in the container. + items: + description: EnvVar represents an environment variable present + in a Container. + properties: + name: + description: Name of the environment variable. Must be + a C_IDENTIFIER. + type: string + value: + description: 'Variable references $(VAR_NAME) are expanded + using the previous defined environment variables in + the container and any service environment variables. + If a variable cannot be resolved, the reference in the + input string will be unchanged. The $(VAR_NAME) syntax + can be escaped with a double $$, ie: $$(VAR_NAME). Escaped + references will never be expanded, regardless of whether + the variable exists or not. Defaults to "".' + type: string + valueFrom: + description: Source for the environment variable's value. + Cannot be used if value is not empty. + properties: + configMapKeyRef: + description: Selects a key of a ConfigMap. + properties: + key: + description: The key to select. + type: string + name: + description: 'Name of the referent. More info: + https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + optional: + description: Specify whether the ConfigMap or + its key must be defined + type: boolean + required: + - key + type: object + fieldRef: + description: 'Selects a field of the pod: supports + metadata.name, metadata.namespace, metadata.labels, + metadata.annotations, spec.nodeName, spec.serviceAccountName, + status.hostIP, status.podIP, status.podIPs.' + properties: + apiVersion: + description: Version of the schema the FieldPath + is written in terms of, defaults to "v1". + type: string + fieldPath: + description: Path of the field to select in the + specified API version. + type: string + required: + - fieldPath + type: object + resourceFieldRef: + description: 'Selects a resource of the container: + only resources limits and requests (limits.cpu, + limits.memory, limits.ephemeral-storage, requests.cpu, + requests.memory and requests.ephemeral-storage) + are currently supported.' + properties: + containerName: + description: 'Container name: required for volumes, + optional for env vars' + type: string + divisor: + anyOf: + - type: integer + - type: string + description: Specifies the output format of the + exposed resources, defaults to "1" + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + description: 'Required: resource to select' + type: string + required: + - resource + type: object + secretKeyRef: + description: Selects a key of a secret in the pod's + namespace + properties: + key: + description: The key of the secret to select from. Must + be a valid secret key. + type: string + name: + description: 'Name of the referent. More info: + https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + optional: + description: Specify whether the Secret or its + key must be defined + type: boolean + required: + - key + type: object + type: object + required: + - name + type: object + type: array + volumes: + description: Additional volume mounts + properties: + defaultMode: + description: Permissions mode. + format: int32 + type: integer + items: + items: + properties: + configMap: + description: ConfigMap mount + properties: + items: + description: ConfigMap mount details + items: + description: Maps a string key to a path within + a volume. + properties: + key: + description: The key to project. + type: string + mode: + description: 'Optional: mode bits to use + on this file, must be a value between + 0 and 0777. If not specified, the volume + defaultMode will be used. This might be + in conflict with other options that affect + the file mode, like fsGroup, and the result + can be other mode bits set.' + format: int32 + type: integer + path: + description: The relative path of the file + to map the key to. May not be an absolute + path. May not contain the path element + '..'. May not start with the string '..'. + type: string + required: + - key + - path + type: object + type: array + mountPath: + description: An absolute path where to mount it + type: string + name: + description: ConfigMap name + type: string + required: + - mountPath + type: object + type: object + type: array + type: object + type: object resources: description: Resources (Requests and Limits) for the Pods. properties: diff --git a/deploy/examples/keycloak/keycloak-with-experimental-settings.yaml b/deploy/examples/keycloak/keycloak-with-experimental-settings.yaml new file mode 100644 index 000000000..d8e8b3a80 --- /dev/null +++ b/deploy/examples/keycloak/keycloak-with-experimental-settings.yaml @@ -0,0 +1,31 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: test-config +data: + test.properties: | + blah=true +--- +apiVersion: keycloak.org/v1alpha1 +kind: Keycloak +metadata: + name: example-keycloak + labels: + app: sso +spec: + instances: 1 + externalAccess: + enabled: True + keycloakDeploymentSpec: + experimental: + args: + - "-Djboss.as.management.blocking.timeout=600" + env: + - name: PROXY_ADDRESS_FORWARDING + value: "false" + volumes: + defaultMode: 0777 + items: + - configMap: + name: test-config + mountPath: /test-config diff --git a/go.sum b/go.sum index 71bff9c80..3362f956a 100644 --- a/go.sum +++ b/go.sum @@ -1715,6 +1715,7 @@ k8s.io/component-base v0.18.0/go.mod h1:u3BCg0z1uskkzrnAKFzulmYaEpZF7XC9Pf/uFyb1 k8s.io/component-base v0.18.2/go.mod h1:kqLlMuhJNHQ9lz8Z7V5bxUUtjFZnrypArGl58gmDfUM= k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= k8s.io/gengo v0.0.0-20190822140433-26a664648505/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= +k8s.io/gengo v0.0.0-20200114144118-36b2048a9120 h1:RPscN6KhmG54S33L+lr3GS+oD1jmchIU0ll519K6FA4= k8s.io/gengo v0.0.0-20200114144118-36b2048a9120/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= k8s.io/klog v0.3.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= diff --git a/pkg/apis/keycloak/v1alpha1/keycloak_types.go b/pkg/apis/keycloak/v1alpha1/keycloak_types.go index c9e2b6778..dec556fce 100644 --- a/pkg/apis/keycloak/v1alpha1/keycloak_types.go +++ b/pkg/apis/keycloak/v1alpha1/keycloak_types.go @@ -5,6 +5,14 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) +type TLSTerminationType string + +var ( + DefaultTLSTermintation TLSTerminationType + ReencryptTLSTerminationType TLSTerminationType = "reencrypt" + PassthroughTLSTerminationType TLSTerminationType = "passthrough" +) + // KeycloakSpec defines the desired state of Keycloak. // +k8s:openapi-gen=true type KeycloakSpec struct { @@ -61,10 +69,10 @@ type KeycloakSpec struct { PodDisruptionBudget PodDisruptionBudgetConfig `json:"podDisruptionBudget,omitempty"` // Resources (Requests and Limits) for KeycloakDeployment. // +optional - KeycloakDeploymentSpec DeploymentSpec `json:"keycloakDeploymentSpec,omitempty"` + KeycloakDeploymentSpec KeycloakDeploymentSpec `json:"keycloakDeploymentSpec,omitempty"` // Resources (Requests and Limits) for PostgresDeployment. // +optional - PostgresDeploymentSpec DeploymentSpec `json:"postgresDeploymentSpec,omitempty"` + PostgresDeploymentSpec PostgresqlDeploymentSpec `json:"postgresDeploymentSpec,omitempty"` // Specify Migration configuration // +optional Migration MigrateConfig `json:"migration,omitempty"` @@ -79,13 +87,58 @@ type DeploymentSpec struct { Resources corev1.ResourceRequirements `json:"resources,omitempty"` } -type TLSTerminationType string +type KeycloakDeploymentSpec struct { + DeploymentSpec `json:",inline"` + // Experimental section + // NOTE: This section might change or get removed without any notice. It may also cause + // the deployment to behave in an unpredictable fashion. Please use with care. + // +optional + Experimental ExperimentalSpec `json:"experimental,omitempty"` +} -var ( - DefaultTLSTermintation TLSTerminationType - ReencryptTLSTerminationType TLSTerminationType = "reencrypt" - PassthroughTLSTerminationType TLSTerminationType = "passthrough" -) +type PostgresqlDeploymentSpec struct { + DeploymentSpec `json:",inline"` +} + +type ExperimentalSpec struct { + // Arguments to the entrypoint. Translates into Container CMD. + // +optional + Args []string `json:"args,omitempty"` + // Container command. Translates into Container ENTRYPOINT. + // +optional + Command []string `json:"command,omitempty"` + // List of environment variables to set in the container. + // +optional + // +patchMergeKey=name + // +patchStrategy=merge + Env []corev1.EnvVar `json:"env,omitempty" patchStrategy:"merge" patchMergeKey:"name"` + // Additional volume mounts + // +optional + Volumes VolumesSpec `json:"volumes,omitempty"` +} + +type VolumesSpec struct { + Items []VolumeSpec `json:"items,omitempty"` + // Permissions mode. + // +optional + DefaultMode *int32 `json:"defaultMode,omitempty"` +} + +type ConfigMapVolumeSpec struct { + // ConfigMap name + Name string `json:"name,omitempty"` + // An absolute path where to mount it + MountPath string `json:"mountPath"` + // ConfigMap mount details + // +optional + Items []corev1.KeyToPath `json:"items,omitempty" protobuf:"bytes,2,rep,name=items"` +} + +type VolumeSpec struct { + // ConfigMap mount + // +optional + ConfigMap *ConfigMapVolumeSpec `json:"configMap,omitempty"` +} type KeycloakExternal struct { // If set to true, this Keycloak will be treated as an external instance. diff --git a/pkg/apis/keycloak/v1alpha1/zz_generated.deepcopy.go b/pkg/apis/keycloak/v1alpha1/zz_generated.deepcopy.go index bd0e7dd0a..e2e28f903 100644 --- a/pkg/apis/keycloak/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/apis/keycloak/v1alpha1/zz_generated.deepcopy.go @@ -5,7 +5,8 @@ package v1alpha1 import ( - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" ) @@ -69,6 +70,29 @@ func (in *BackupConfig) DeepCopy() *BackupConfig { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ConfigMapVolumeSpec) DeepCopyInto(out *ConfigMapVolumeSpec) { + *out = *in + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]v1.KeyToPath, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ConfigMapVolumeSpec. +func (in *ConfigMapVolumeSpec) DeepCopy() *ConfigMapVolumeSpec { + if in == nil { + return nil + } + out := new(ConfigMapVolumeSpec) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *DeploymentSpec) DeepCopyInto(out *DeploymentSpec) { *out = *in @@ -86,6 +110,40 @@ func (in *DeploymentSpec) DeepCopy() *DeploymentSpec { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ExperimentalSpec) DeepCopyInto(out *ExperimentalSpec) { + *out = *in + if in.Args != nil { + in, out := &in.Args, &out.Args + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.Command != nil { + in, out := &in.Command, &out.Command + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.Env != nil { + in, out := &in.Env, &out.Env + *out = make([]v1.EnvVar, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + in.Volumes.DeepCopyInto(&out.Volumes) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExperimentalSpec. +func (in *ExperimentalSpec) DeepCopy() *ExperimentalSpec { + if in == nil { + return nil + } + out := new(ExperimentalSpec) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *FederatedIdentity) DeepCopyInto(out *FederatedIdentity) { *out = *in @@ -582,7 +640,7 @@ func (in *KeycloakBackupSpec) DeepCopyInto(out *KeycloakBackupSpec) { out.AWS = in.AWS if in.InstanceSelector != nil { in, out := &in.InstanceSelector, &out.InstanceSelector - *out = new(v1.LabelSelector) + *out = new(metav1.LabelSelector) (*in).DeepCopyInto(*out) } if in.StorageClassName != nil { @@ -730,7 +788,7 @@ func (in *KeycloakClientSpec) DeepCopyInto(out *KeycloakClientSpec) { *out = *in if in.RealmSelector != nil { in, out := &in.RealmSelector, &out.RealmSelector - *out = new(v1.LabelSelector) + *out = new(metav1.LabelSelector) (*in).DeepCopyInto(*out) } if in.Client != nil { @@ -798,6 +856,24 @@ func (in *KeycloakCredential) DeepCopy() *KeycloakCredential { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *KeycloakDeploymentSpec) DeepCopyInto(out *KeycloakDeploymentSpec) { + *out = *in + in.DeploymentSpec.DeepCopyInto(&out.DeploymentSpec) + in.Experimental.DeepCopyInto(&out.Experimental) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KeycloakDeploymentSpec. +func (in *KeycloakDeploymentSpec) DeepCopy() *KeycloakDeploymentSpec { + if in == nil { + return nil + } + out := new(KeycloakDeploymentSpec) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *KeycloakExternal) DeepCopyInto(out *KeycloakExternal) { *out = *in @@ -991,7 +1067,7 @@ func (in *KeycloakRealmSpec) DeepCopyInto(out *KeycloakRealmSpec) { *out = *in if in.InstanceSelector != nil { in, out := &in.InstanceSelector, &out.InstanceSelector - *out = new(v1.LabelSelector) + *out = new(metav1.LabelSelector) (*in).DeepCopyInto(*out) } if in.Realm != nil { @@ -1200,7 +1276,7 @@ func (in *KeycloakUserSpec) DeepCopyInto(out *KeycloakUserSpec) { *out = *in if in.RealmSelector != nil { in, out := &in.RealmSelector, &out.RealmSelector - *out = new(v1.LabelSelector) + *out = new(metav1.LabelSelector) (*in).DeepCopyInto(*out) } in.User.DeepCopyInto(&out.User) @@ -1266,6 +1342,23 @@ func (in *PodDisruptionBudgetConfig) DeepCopy() *PodDisruptionBudgetConfig { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PostgresqlDeploymentSpec) DeepCopyInto(out *PostgresqlDeploymentSpec) { + *out = *in + in.DeploymentSpec.DeepCopyInto(&out.DeploymentSpec) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PostgresqlDeploymentSpec. +func (in *PostgresqlDeploymentSpec) DeepCopy() *PostgresqlDeploymentSpec { + if in == nil { + return nil + } + out := new(PostgresqlDeploymentSpec) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *RedirectorIdentityProviderOverride) DeepCopyInto(out *RedirectorIdentityProviderOverride) { *out = *in @@ -1297,3 +1390,52 @@ func (in *TokenResponse) DeepCopy() *TokenResponse { in.DeepCopyInto(out) return out } + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *VolumeSpec) DeepCopyInto(out *VolumeSpec) { + *out = *in + if in.ConfigMap != nil { + in, out := &in.ConfigMap, &out.ConfigMap + *out = new(ConfigMapVolumeSpec) + (*in).DeepCopyInto(*out) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VolumeSpec. +func (in *VolumeSpec) DeepCopy() *VolumeSpec { + if in == nil { + return nil + } + out := new(VolumeSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *VolumesSpec) DeepCopyInto(out *VolumesSpec) { + *out = *in + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]VolumeSpec, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.DefaultMode != nil { + in, out := &in.DefaultMode, &out.DefaultMode + *out = new(int32) + **out = **in + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VolumesSpec. +func (in *VolumesSpec) DeepCopy() *VolumesSpec { + if in == nil { + return nil + } + out := new(VolumesSpec) + in.DeepCopyInto(out) + return out +} diff --git a/pkg/apis/keycloak/v1alpha1/zz_generated.openapi.go b/pkg/apis/keycloak/v1alpha1/zz_generated.openapi.go index 7774be8e3..a1cdfafd8 100644 --- a/pkg/apis/keycloak/v1alpha1/zz_generated.openapi.go +++ b/pkg/apis/keycloak/v1alpha1/zz_generated.openapi.go @@ -13,22 +13,22 @@ import ( func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenAPIDefinition { return map[string]common.OpenAPIDefinition{ - "./pkg/apis/keycloak/v1alpha1.Keycloak": schema_pkg_apis_keycloak_v1alpha1_Keycloak(ref), - "./pkg/apis/keycloak/v1alpha1.KeycloakAWSSpec": schema_pkg_apis_keycloak_v1alpha1_KeycloakAWSSpec(ref), - "./pkg/apis/keycloak/v1alpha1.KeycloakBackup": schema_pkg_apis_keycloak_v1alpha1_KeycloakBackup(ref), - "./pkg/apis/keycloak/v1alpha1.KeycloakBackupSpec": schema_pkg_apis_keycloak_v1alpha1_KeycloakBackupSpec(ref), - "./pkg/apis/keycloak/v1alpha1.KeycloakBackupStatus": schema_pkg_apis_keycloak_v1alpha1_KeycloakBackupStatus(ref), - "./pkg/apis/keycloak/v1alpha1.KeycloakClient": schema_pkg_apis_keycloak_v1alpha1_KeycloakClient(ref), - "./pkg/apis/keycloak/v1alpha1.KeycloakClientSpec": schema_pkg_apis_keycloak_v1alpha1_KeycloakClientSpec(ref), - "./pkg/apis/keycloak/v1alpha1.KeycloakClientStatus": schema_pkg_apis_keycloak_v1alpha1_KeycloakClientStatus(ref), - "./pkg/apis/keycloak/v1alpha1.KeycloakRealm": schema_pkg_apis_keycloak_v1alpha1_KeycloakRealm(ref), - "./pkg/apis/keycloak/v1alpha1.KeycloakRealmSpec": schema_pkg_apis_keycloak_v1alpha1_KeycloakRealmSpec(ref), - "./pkg/apis/keycloak/v1alpha1.KeycloakRealmStatus": schema_pkg_apis_keycloak_v1alpha1_KeycloakRealmStatus(ref), - "./pkg/apis/keycloak/v1alpha1.KeycloakSpec": schema_pkg_apis_keycloak_v1alpha1_KeycloakSpec(ref), - "./pkg/apis/keycloak/v1alpha1.KeycloakStatus": schema_pkg_apis_keycloak_v1alpha1_KeycloakStatus(ref), - "./pkg/apis/keycloak/v1alpha1.KeycloakUser": schema_pkg_apis_keycloak_v1alpha1_KeycloakUser(ref), - "./pkg/apis/keycloak/v1alpha1.KeycloakUserSpec": schema_pkg_apis_keycloak_v1alpha1_KeycloakUserSpec(ref), - "./pkg/apis/keycloak/v1alpha1.KeycloakUserStatus": schema_pkg_apis_keycloak_v1alpha1_KeycloakUserStatus(ref), + "github.com/keycloak/keycloak-operator/pkg/apis/keycloak/v1alpha1.Keycloak": schema_pkg_apis_keycloak_v1alpha1_Keycloak(ref), + "github.com/keycloak/keycloak-operator/pkg/apis/keycloak/v1alpha1.KeycloakAWSSpec": schema_pkg_apis_keycloak_v1alpha1_KeycloakAWSSpec(ref), + "github.com/keycloak/keycloak-operator/pkg/apis/keycloak/v1alpha1.KeycloakBackup": schema_pkg_apis_keycloak_v1alpha1_KeycloakBackup(ref), + "github.com/keycloak/keycloak-operator/pkg/apis/keycloak/v1alpha1.KeycloakBackupSpec": schema_pkg_apis_keycloak_v1alpha1_KeycloakBackupSpec(ref), + "github.com/keycloak/keycloak-operator/pkg/apis/keycloak/v1alpha1.KeycloakBackupStatus": schema_pkg_apis_keycloak_v1alpha1_KeycloakBackupStatus(ref), + "github.com/keycloak/keycloak-operator/pkg/apis/keycloak/v1alpha1.KeycloakClient": schema_pkg_apis_keycloak_v1alpha1_KeycloakClient(ref), + "github.com/keycloak/keycloak-operator/pkg/apis/keycloak/v1alpha1.KeycloakClientSpec": schema_pkg_apis_keycloak_v1alpha1_KeycloakClientSpec(ref), + "github.com/keycloak/keycloak-operator/pkg/apis/keycloak/v1alpha1.KeycloakClientStatus": schema_pkg_apis_keycloak_v1alpha1_KeycloakClientStatus(ref), + "github.com/keycloak/keycloak-operator/pkg/apis/keycloak/v1alpha1.KeycloakRealm": schema_pkg_apis_keycloak_v1alpha1_KeycloakRealm(ref), + "github.com/keycloak/keycloak-operator/pkg/apis/keycloak/v1alpha1.KeycloakRealmSpec": schema_pkg_apis_keycloak_v1alpha1_KeycloakRealmSpec(ref), + "github.com/keycloak/keycloak-operator/pkg/apis/keycloak/v1alpha1.KeycloakRealmStatus": schema_pkg_apis_keycloak_v1alpha1_KeycloakRealmStatus(ref), + "github.com/keycloak/keycloak-operator/pkg/apis/keycloak/v1alpha1.KeycloakSpec": schema_pkg_apis_keycloak_v1alpha1_KeycloakSpec(ref), + "github.com/keycloak/keycloak-operator/pkg/apis/keycloak/v1alpha1.KeycloakStatus": schema_pkg_apis_keycloak_v1alpha1_KeycloakStatus(ref), + "github.com/keycloak/keycloak-operator/pkg/apis/keycloak/v1alpha1.KeycloakUser": schema_pkg_apis_keycloak_v1alpha1_KeycloakUser(ref), + "github.com/keycloak/keycloak-operator/pkg/apis/keycloak/v1alpha1.KeycloakUserSpec": schema_pkg_apis_keycloak_v1alpha1_KeycloakUserSpec(ref), + "github.com/keycloak/keycloak-operator/pkg/apis/keycloak/v1alpha1.KeycloakUserStatus": schema_pkg_apis_keycloak_v1alpha1_KeycloakUserStatus(ref), } } @@ -60,19 +60,19 @@ func schema_pkg_apis_keycloak_v1alpha1_Keycloak(ref common.ReferenceCallback) co }, "spec": { SchemaProps: spec.SchemaProps{ - Ref: ref("./pkg/apis/keycloak/v1alpha1.KeycloakSpec"), + Ref: ref("github.com/keycloak/keycloak-operator/pkg/apis/keycloak/v1alpha1.KeycloakSpec"), }, }, "status": { SchemaProps: spec.SchemaProps{ - Ref: ref("./pkg/apis/keycloak/v1alpha1.KeycloakStatus"), + Ref: ref("github.com/keycloak/keycloak-operator/pkg/apis/keycloak/v1alpha1.KeycloakStatus"), }, }, }, }, }, Dependencies: []string{ - "./pkg/apis/keycloak/v1alpha1.KeycloakSpec", "./pkg/apis/keycloak/v1alpha1.KeycloakStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, + "github.com/keycloak/keycloak-operator/pkg/apis/keycloak/v1alpha1.KeycloakSpec", "github.com/keycloak/keycloak-operator/pkg/apis/keycloak/v1alpha1.KeycloakStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, } } @@ -138,19 +138,19 @@ func schema_pkg_apis_keycloak_v1alpha1_KeycloakBackup(ref common.ReferenceCallba }, "spec": { SchemaProps: spec.SchemaProps{ - Ref: ref("./pkg/apis/keycloak/v1alpha1.KeycloakBackupSpec"), + Ref: ref("github.com/keycloak/keycloak-operator/pkg/apis/keycloak/v1alpha1.KeycloakBackupSpec"), }, }, "status": { SchemaProps: spec.SchemaProps{ - Ref: ref("./pkg/apis/keycloak/v1alpha1.KeycloakBackupStatus"), + Ref: ref("github.com/keycloak/keycloak-operator/pkg/apis/keycloak/v1alpha1.KeycloakBackupStatus"), }, }, }, }, }, Dependencies: []string{ - "./pkg/apis/keycloak/v1alpha1.KeycloakBackupSpec", "./pkg/apis/keycloak/v1alpha1.KeycloakBackupStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, + "github.com/keycloak/keycloak-operator/pkg/apis/keycloak/v1alpha1.KeycloakBackupSpec", "github.com/keycloak/keycloak-operator/pkg/apis/keycloak/v1alpha1.KeycloakBackupStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, } } @@ -171,7 +171,7 @@ func schema_pkg_apis_keycloak_v1alpha1_KeycloakBackupSpec(ref common.ReferenceCa "aws": { SchemaProps: spec.SchemaProps{ Description: "If provided, an automatic database backup will be created on AWS S3 instead of a local Persistent Volume. If this property is not provided - a local Persistent Volume backup will be chosen.", - Ref: ref("./pkg/apis/keycloak/v1alpha1.KeycloakAWSSpec"), + Ref: ref("github.com/keycloak/keycloak-operator/pkg/apis/keycloak/v1alpha1.KeycloakAWSSpec"), }, }, "instanceSelector": { @@ -191,7 +191,7 @@ func schema_pkg_apis_keycloak_v1alpha1_KeycloakBackupSpec(ref common.ReferenceCa }, }, Dependencies: []string{ - "./pkg/apis/keycloak/v1alpha1.KeycloakAWSSpec", "k8s.io/apimachinery/pkg/apis/meta/v1.LabelSelector"}, + "github.com/keycloak/keycloak-operator/pkg/apis/keycloak/v1alpha1.KeycloakAWSSpec", "k8s.io/apimachinery/pkg/apis/meta/v1.LabelSelector"}, } } @@ -280,19 +280,19 @@ func schema_pkg_apis_keycloak_v1alpha1_KeycloakClient(ref common.ReferenceCallba }, "spec": { SchemaProps: spec.SchemaProps{ - Ref: ref("./pkg/apis/keycloak/v1alpha1.KeycloakClientSpec"), + Ref: ref("github.com/keycloak/keycloak-operator/pkg/apis/keycloak/v1alpha1.KeycloakClientSpec"), }, }, "status": { SchemaProps: spec.SchemaProps{ - Ref: ref("./pkg/apis/keycloak/v1alpha1.KeycloakClientStatus"), + Ref: ref("github.com/keycloak/keycloak-operator/pkg/apis/keycloak/v1alpha1.KeycloakClientStatus"), }, }, }, }, }, Dependencies: []string{ - "./pkg/apis/keycloak/v1alpha1.KeycloakClientSpec", "./pkg/apis/keycloak/v1alpha1.KeycloakClientStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, + "github.com/keycloak/keycloak-operator/pkg/apis/keycloak/v1alpha1.KeycloakClientSpec", "github.com/keycloak/keycloak-operator/pkg/apis/keycloak/v1alpha1.KeycloakClientStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, } } @@ -312,7 +312,7 @@ func schema_pkg_apis_keycloak_v1alpha1_KeycloakClientSpec(ref common.ReferenceCa "client": { SchemaProps: spec.SchemaProps{ Description: "Keycloak Client REST object.", - Ref: ref("./pkg/apis/keycloak/v1alpha1.KeycloakAPIClient"), + Ref: ref("github.com/keycloak/keycloak-operator/pkg/apis/keycloak/v1alpha1.KeycloakAPIClient"), }, }, }, @@ -320,7 +320,7 @@ func schema_pkg_apis_keycloak_v1alpha1_KeycloakClientSpec(ref common.ReferenceCa }, }, Dependencies: []string{ - "./pkg/apis/keycloak/v1alpha1.KeycloakAPIClient", "k8s.io/apimachinery/pkg/apis/meta/v1.LabelSelector"}, + "github.com/keycloak/keycloak-operator/pkg/apis/keycloak/v1alpha1.KeycloakAPIClient", "k8s.io/apimachinery/pkg/apis/meta/v1.LabelSelector"}, } } @@ -409,19 +409,19 @@ func schema_pkg_apis_keycloak_v1alpha1_KeycloakRealm(ref common.ReferenceCallbac }, "spec": { SchemaProps: spec.SchemaProps{ - Ref: ref("./pkg/apis/keycloak/v1alpha1.KeycloakRealmSpec"), + Ref: ref("github.com/keycloak/keycloak-operator/pkg/apis/keycloak/v1alpha1.KeycloakRealmSpec"), }, }, "status": { SchemaProps: spec.SchemaProps{ - Ref: ref("./pkg/apis/keycloak/v1alpha1.KeycloakRealmStatus"), + Ref: ref("github.com/keycloak/keycloak-operator/pkg/apis/keycloak/v1alpha1.KeycloakRealmStatus"), }, }, }, }, }, Dependencies: []string{ - "./pkg/apis/keycloak/v1alpha1.KeycloakRealmSpec", "./pkg/apis/keycloak/v1alpha1.KeycloakRealmStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, + "github.com/keycloak/keycloak-operator/pkg/apis/keycloak/v1alpha1.KeycloakRealmSpec", "github.com/keycloak/keycloak-operator/pkg/apis/keycloak/v1alpha1.KeycloakRealmStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, } } @@ -448,7 +448,7 @@ func schema_pkg_apis_keycloak_v1alpha1_KeycloakRealmSpec(ref common.ReferenceCal "realm": { SchemaProps: spec.SchemaProps{ Description: "Keycloak Realm REST object.", - Ref: ref("./pkg/apis/keycloak/v1alpha1.KeycloakAPIRealm"), + Ref: ref("github.com/keycloak/keycloak-operator/pkg/apis/keycloak/v1alpha1.KeycloakAPIRealm"), }, }, "realmOverrides": { @@ -458,7 +458,7 @@ func schema_pkg_apis_keycloak_v1alpha1_KeycloakRealmSpec(ref common.ReferenceCal Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ SchemaProps: spec.SchemaProps{ - Ref: ref("./pkg/apis/keycloak/v1alpha1.RedirectorIdentityProviderOverride"), + Ref: ref("github.com/keycloak/keycloak-operator/pkg/apis/keycloak/v1alpha1.RedirectorIdentityProviderOverride"), }, }, }, @@ -469,7 +469,7 @@ func schema_pkg_apis_keycloak_v1alpha1_KeycloakRealmSpec(ref common.ReferenceCal }, }, Dependencies: []string{ - "./pkg/apis/keycloak/v1alpha1.KeycloakAPIRealm", "./pkg/apis/keycloak/v1alpha1.RedirectorIdentityProviderOverride", "k8s.io/apimachinery/pkg/apis/meta/v1.LabelSelector"}, + "github.com/keycloak/keycloak-operator/pkg/apis/keycloak/v1alpha1.KeycloakAPIRealm", "github.com/keycloak/keycloak-operator/pkg/apis/keycloak/v1alpha1.RedirectorIdentityProviderOverride", "k8s.io/apimachinery/pkg/apis/meta/v1.LabelSelector"}, } } @@ -553,7 +553,7 @@ func schema_pkg_apis_keycloak_v1alpha1_KeycloakSpec(ref common.ReferenceCallback "external": { SchemaProps: spec.SchemaProps{ Description: "Contains configuration for external Keycloak instances. Unmanaged needs to be set to true to use this.", - Ref: ref("./pkg/apis/keycloak/v1alpha1.KeycloakExternal"), + Ref: ref("github.com/keycloak/keycloak-operator/pkg/apis/keycloak/v1alpha1.KeycloakExternal"), }, }, "extensions": { @@ -585,13 +585,13 @@ func schema_pkg_apis_keycloak_v1alpha1_KeycloakSpec(ref common.ReferenceCallback "externalAccess": { SchemaProps: spec.SchemaProps{ Description: "Controls external Ingress/Route settings.", - Ref: ref("./pkg/apis/keycloak/v1alpha1.KeycloakExternalAccess"), + Ref: ref("github.com/keycloak/keycloak-operator/pkg/apis/keycloak/v1alpha1.KeycloakExternalAccess"), }, }, "externalDatabase": { SchemaProps: spec.SchemaProps{ Description: "Controls external database settings. Using an external database requires providing a secret containing credentials as well as connection details. Here's an example of such secret:\n\n apiVersion: v1\n kind: Secret\n metadata:\n name: keycloak-db-secret\n namespace: keycloak\n stringData:\n POSTGRES_DATABASE: \n POSTGRES_EXTERNAL_ADDRESS: \n POSTGRES_EXTERNAL_PORT: \n # Strongly recommended to use <'Keycloak CR Name'-postgresql>\n POSTGRES_HOST: \n POSTGRES_PASSWORD: \n # Required for AWS Backup functionality\n POSTGRES_SUPERUSER: true\n POSTGRES_USERNAME: \n type: Opaque\n\nBoth POSTGRES_EXTERNAL_ADDRESS and POSTGRES_EXTERNAL_PORT are specifically required for creating connection to the external database. The secret name is created using the following convention:\n -db-secret\n\nFor more information, please refer to the Operator documentation.", - Ref: ref("./pkg/apis/keycloak/v1alpha1.KeycloakExternalDatabase"), + Ref: ref("github.com/keycloak/keycloak-operator/pkg/apis/keycloak/v1alpha1.KeycloakExternalDatabase"), }, }, "profile": { @@ -604,25 +604,25 @@ func schema_pkg_apis_keycloak_v1alpha1_KeycloakSpec(ref common.ReferenceCallback "podDisruptionBudget": { SchemaProps: spec.SchemaProps{ Description: "Specify PodDisruptionBudget configuration.", - Ref: ref("./pkg/apis/keycloak/v1alpha1.PodDisruptionBudgetConfig"), + Ref: ref("github.com/keycloak/keycloak-operator/pkg/apis/keycloak/v1alpha1.PodDisruptionBudgetConfig"), }, }, "keycloakDeploymentSpec": { SchemaProps: spec.SchemaProps{ Description: "Resources (Requests and Limits) for KeycloakDeployment.", - Ref: ref("./pkg/apis/keycloak/v1alpha1.DeploymentSpec"), + Ref: ref("github.com/keycloak/keycloak-operator/pkg/apis/keycloak/v1alpha1.KeycloakDeploymentSpec"), }, }, "postgresDeploymentSpec": { SchemaProps: spec.SchemaProps{ Description: "Resources (Requests and Limits) for PostgresDeployment.", - Ref: ref("./pkg/apis/keycloak/v1alpha1.DeploymentSpec"), + Ref: ref("github.com/keycloak/keycloak-operator/pkg/apis/keycloak/v1alpha1.PostgresqlDeploymentSpec"), }, }, "migration": { SchemaProps: spec.SchemaProps{ Description: "Specify Migration configuration", - Ref: ref("./pkg/apis/keycloak/v1alpha1.MigrateConfig"), + Ref: ref("github.com/keycloak/keycloak-operator/pkg/apis/keycloak/v1alpha1.MigrateConfig"), }, }, "storageClassName": { @@ -636,7 +636,7 @@ func schema_pkg_apis_keycloak_v1alpha1_KeycloakSpec(ref common.ReferenceCallback }, }, Dependencies: []string{ - "./pkg/apis/keycloak/v1alpha1.DeploymentSpec", "./pkg/apis/keycloak/v1alpha1.KeycloakExternal", "./pkg/apis/keycloak/v1alpha1.KeycloakExternalAccess", "./pkg/apis/keycloak/v1alpha1.KeycloakExternalDatabase", "./pkg/apis/keycloak/v1alpha1.MigrateConfig", "./pkg/apis/keycloak/v1alpha1.PodDisruptionBudgetConfig"}, + "github.com/keycloak/keycloak-operator/pkg/apis/keycloak/v1alpha1.KeycloakDeploymentSpec", "github.com/keycloak/keycloak-operator/pkg/apis/keycloak/v1alpha1.KeycloakExternal", "github.com/keycloak/keycloak-operator/pkg/apis/keycloak/v1alpha1.KeycloakExternalAccess", "github.com/keycloak/keycloak-operator/pkg/apis/keycloak/v1alpha1.KeycloakExternalDatabase", "github.com/keycloak/keycloak-operator/pkg/apis/keycloak/v1alpha1.MigrateConfig", "github.com/keycloak/keycloak-operator/pkg/apis/keycloak/v1alpha1.PodDisruptionBudgetConfig", "github.com/keycloak/keycloak-operator/pkg/apis/keycloak/v1alpha1.PostgresqlDeploymentSpec"}, } } @@ -746,19 +746,19 @@ func schema_pkg_apis_keycloak_v1alpha1_KeycloakUser(ref common.ReferenceCallback }, "spec": { SchemaProps: spec.SchemaProps{ - Ref: ref("./pkg/apis/keycloak/v1alpha1.KeycloakUserSpec"), + Ref: ref("github.com/keycloak/keycloak-operator/pkg/apis/keycloak/v1alpha1.KeycloakUserSpec"), }, }, "status": { SchemaProps: spec.SchemaProps{ - Ref: ref("./pkg/apis/keycloak/v1alpha1.KeycloakUserStatus"), + Ref: ref("github.com/keycloak/keycloak-operator/pkg/apis/keycloak/v1alpha1.KeycloakUserStatus"), }, }, }, }, }, Dependencies: []string{ - "./pkg/apis/keycloak/v1alpha1.KeycloakUserSpec", "./pkg/apis/keycloak/v1alpha1.KeycloakUserStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, + "github.com/keycloak/keycloak-operator/pkg/apis/keycloak/v1alpha1.KeycloakUserSpec", "github.com/keycloak/keycloak-operator/pkg/apis/keycloak/v1alpha1.KeycloakUserStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, } } @@ -778,7 +778,7 @@ func schema_pkg_apis_keycloak_v1alpha1_KeycloakUserSpec(ref common.ReferenceCall "user": { SchemaProps: spec.SchemaProps{ Description: "Keycloak User REST object.", - Ref: ref("./pkg/apis/keycloak/v1alpha1.KeycloakAPIUser"), + Ref: ref("github.com/keycloak/keycloak-operator/pkg/apis/keycloak/v1alpha1.KeycloakAPIUser"), }, }, }, @@ -786,7 +786,7 @@ func schema_pkg_apis_keycloak_v1alpha1_KeycloakUserSpec(ref common.ReferenceCall }, }, Dependencies: []string{ - "./pkg/apis/keycloak/v1alpha1.KeycloakAPIUser", "k8s.io/apimachinery/pkg/apis/meta/v1.LabelSelector"}, + "github.com/keycloak/keycloak-operator/pkg/apis/keycloak/v1alpha1.KeycloakAPIUser", "k8s.io/apimachinery/pkg/apis/meta/v1.LabelSelector"}, } } diff --git a/pkg/controller/keycloak/keycloak_reconciler_test.go b/pkg/controller/keycloak/keycloak_reconciler_test.go index 8ce850032..b15868bb3 100644 --- a/pkg/controller/keycloak/keycloak_reconciler_test.go +++ b/pkg/controller/keycloak/keycloak_reconciler_test.go @@ -555,16 +555,20 @@ func TestKeycloakReconciler_Test_Setting_Resources(t *testing.T) { resourceListPostgres[v1.ResourceCPU] = resource50m resourceListPostgres[v1.ResourceMemory] = resource100Mi - cr.Spec.KeycloakDeploymentSpec = v1alpha1.DeploymentSpec{ - Resources: v1.ResourceRequirements{ - Requests: resourceListKeycloak, - Limits: resourceListKeycloak, + cr.Spec.KeycloakDeploymentSpec = v1alpha1.KeycloakDeploymentSpec{ + DeploymentSpec: v1alpha1.DeploymentSpec{ + Resources: v1.ResourceRequirements{ + Requests: resourceListKeycloak, + Limits: resourceListKeycloak, + }, }, } - cr.Spec.PostgresDeploymentSpec = v1alpha1.DeploymentSpec{ - Resources: v1.ResourceRequirements{ - Requests: resourceListPostgres, - Limits: resourceListPostgres, + cr.Spec.PostgresDeploymentSpec = v1alpha1.PostgresqlDeploymentSpec{ + DeploymentSpec: v1alpha1.DeploymentSpec{ + Resources: v1.ResourceRequirements{ + Requests: resourceListPostgres, + Limits: resourceListPostgres, + }, }, } diff --git a/pkg/model/keycloak_deployment.go b/pkg/model/keycloak_deployment.go index 42e234826..067ca6c6c 100644 --- a/pkg/model/keycloak_deployment.go +++ b/pkg/model/keycloak_deployment.go @@ -161,6 +161,11 @@ func getKeycloakEnv(cr *v1alpha1.Keycloak, dbSecret *v1.Secret) []v1.EnvVar { }) } + if len(cr.Spec.KeycloakDeploymentSpec.Experimental.Env) > 0 { + // We override Keycloak pre-defined envs with what user specified. Not the other way around. + env = MergeEnvs(cr.Spec.KeycloakDeploymentSpec.Experimental.Env, env) + } + return env } @@ -193,7 +198,7 @@ func KeycloakDeployment(cr *v1alpha1.Keycloak, dbSecret *v1.Secret) *v13.Statefu }, Spec: v1.PodSpec{ InitContainers: KeycloakExtensionsInitContainers(cr), - Volumes: KeycloakVolumes(), + Volumes: KeycloakVolumes(cr), Containers: []v1.Container{ { Name: KeycloakDeploymentName, @@ -212,10 +217,12 @@ func KeycloakDeployment(cr *v1alpha1.Keycloak, dbSecret *v1.Secret) *v13.Statefu Protocol: "TCP", }, }, - VolumeMounts: KeycloakVolumeMounts(KeycloakExtensionPath), + VolumeMounts: KeycloakVolumeMounts(cr, KeycloakExtensionPath), LivenessProbe: livenessProbe(), ReadinessProbe: readinessProbe(), Env: getKeycloakEnv(cr, dbSecret), + Args: cr.Spec.KeycloakDeploymentSpec.Experimental.Args, + Command: cr.Spec.KeycloakDeploymentSpec.Experimental.Command, Resources: getResources(cr), }, }, @@ -236,11 +243,13 @@ func KeycloakDeploymentReconciled(cr *v1alpha1.Keycloak, currentState *v13.State reconciled := currentState.DeepCopy() reconciled.ResourceVersion = currentState.ResourceVersion reconciled.Spec.Replicas = SanitizeNumberOfReplicas(cr.Spec.Instances, false) - reconciled.Spec.Template.Spec.Volumes = KeycloakVolumes() + reconciled.Spec.Template.Spec.Volumes = KeycloakVolumes(cr) reconciled.Spec.Template.Spec.Containers = []v1.Container{ { - Name: KeycloakDeploymentName, - Image: Images.Images[KeycloakImage], + Name: KeycloakDeploymentName, + Image: Images.Images[KeycloakImage], + Args: cr.Spec.KeycloakDeploymentSpec.Experimental.Args, + Command: cr.Spec.KeycloakDeploymentSpec.Experimental.Command, Ports: []v1.ContainerPort{ { ContainerPort: KeycloakServicePort, @@ -255,7 +264,7 @@ func KeycloakDeploymentReconciled(cr *v1alpha1.Keycloak, currentState *v13.State Protocol: "TCP", }, }, - VolumeMounts: KeycloakVolumeMounts(KeycloakExtensionPath), + VolumeMounts: KeycloakVolumeMounts(cr, KeycloakExtensionPath), LivenessProbe: livenessProbe(), ReadinessProbe: readinessProbe(), Env: getKeycloakEnv(cr, dbSecret), @@ -266,8 +275,8 @@ func KeycloakDeploymentReconciled(cr *v1alpha1.Keycloak, currentState *v13.State return reconciled } -func KeycloakVolumeMounts(extensionsPath string) []v1.VolumeMount { - return []v1.VolumeMount{ +func KeycloakVolumeMounts(cr *v1alpha1.Keycloak, extensionsPath string) []v1.VolumeMount { + mountedVolumes := []v1.VolumeMount{ { Name: ServingCertSecretName, MountPath: "/etc/x509/https", @@ -282,10 +291,29 @@ func KeycloakVolumeMounts(extensionsPath string) []v1.VolumeMount { MountPath: "/probes", }, } + + mountedVolumes = addVolumeMountsFromKeycloakCR(cr, mountedVolumes) + + return mountedVolumes } -func KeycloakVolumes() []v1.Volume { - return []v1.Volume{ +func addVolumeMountsFromKeycloakCR(cr *v1alpha1.Keycloak, mountedVolumes []v1.VolumeMount) []v1.VolumeMount { + if cr.Spec.KeycloakDeploymentSpec.Experimental.Volumes.Items != nil { + for _, v := range cr.Spec.KeycloakDeploymentSpec.Experimental.Volumes.Items { + if v.ConfigMap != nil { + configMapMount := v1.VolumeMount{ + Name: v.ConfigMap.Name, + MountPath: v.ConfigMap.MountPath, + } + mountedVolumes = append(mountedVolumes, configMapMount) + } + } + } + return mountedVolumes +} + +func KeycloakVolumes(cr *v1alpha1.Keycloak) []v1.Volume { + volumes := []v1.Volume{ { Name: ServingCertSecretName, VolumeSource: v1.VolumeSource{ @@ -313,6 +341,41 @@ func KeycloakVolumes() []v1.Volume { }, }, } + + volumes = addVolumesFromKeycloakCR(cr, volumes) + + return volumes +} + +func addVolumesFromKeycloakCR(cr *v1alpha1.Keycloak, volumes []v1.Volume) []v1.Volume { + if cr.Spec.KeycloakDeploymentSpec.Experimental.Volumes.Items != nil { + for _, v := range cr.Spec.KeycloakDeploymentSpec.Experimental.Volumes.Items { + // We could also add multiple ProjectedVolumeSources but then we lose the ability + // to specify different paths to mount them. This way it's more flexible. + if v.ConfigMap != nil { + configMapVolume := v1.Volume{ + Name: v.ConfigMap.Name, + VolumeSource: v1.VolumeSource{ + Projected: &v1.ProjectedVolumeSource{ + Sources: []v1.VolumeProjection{ + { + ConfigMap: &v1.ConfigMapProjection{ + LocalObjectReference: v1.LocalObjectReference{ + Name: v.ConfigMap.Name, + }, + Items: v.ConfigMap.Items, + }, + }, + }, + DefaultMode: cr.Spec.KeycloakDeploymentSpec.Experimental.Volumes.DefaultMode, + }, + }, + } + volumes = append(volumes, configMapVolume) + } + } + } + return volumes } func livenessProbe() *v1.Probe { diff --git a/pkg/model/keycloak_deployment_test.go b/pkg/model/keycloak_deployment_test.go new file mode 100644 index 000000000..f86e3d6d1 --- /dev/null +++ b/pkg/model/keycloak_deployment_test.go @@ -0,0 +1,157 @@ +package model + +import ( + "testing" + + "github.com/keycloak/keycloak-operator/pkg/apis/keycloak/v1alpha1" + "github.com/stretchr/testify/assert" + v13 "k8s.io/api/apps/v1" + v1 "k8s.io/api/core/v1" +) + +type createDeploymentStatefulSet func(*v1alpha1.Keycloak, *v1.Secret) *v13.StatefulSet + +func TestKeycloakDeployment_testExperimentalEnvs(t *testing.T) { + testExperimentalEnvs(t, KeycloakDeployment) +} + +func TestKeycloakDeployment_testExperimentalArgs(t *testing.T) { + testExperimentalArgs(t, KeycloakDeployment) +} + +func TestKeycloakDeployment_testExperimentalCommand(t *testing.T) { + testExperimentalCommand(t, KeycloakDeployment) +} + +func TestKeycloakDeployment_testExperimentalVolumesWithConfigMaps(t *testing.T) { + testExperimentalVolumesWithConfigMaps(t, KeycloakDeployment) +} + +func testExperimentalEnvs(t *testing.T, deploymentFunction createDeploymentStatefulSet) { + //given + dbSecret := &v1.Secret{} + cr := &v1alpha1.Keycloak{ + Spec: v1alpha1.KeycloakSpec{ + KeycloakDeploymentSpec: v1alpha1.KeycloakDeploymentSpec{ + Experimental: v1alpha1.ExperimentalSpec{ + Env: []v1.EnvVar{ + { + // New value + Name: "testName", + Value: "testValue", + }, + { + // An overridden value + Name: "DB_SCHEMA", + Value: "test", + }, + }, + }, + }, + }, + } + + //when + envs := deploymentFunction(cr, dbSecret).Spec.Template.Spec.Containers[0].Env + + //then + hasTestNameKey := false + testNameValue := "" + dbSchemaValue := "" + for _, v := range envs { + if v.Name == "testName" { + hasTestNameKey = true + testNameValue = v.Value + } + if v.Name == "DB_SCHEMA" { + dbSchemaValue = v.Value + } + } + assert.True(t, hasTestNameKey) + assert.Equal(t, "testValue", testNameValue) + assert.Equal(t, "test", dbSchemaValue) + assert.True(t, len(envs) > 1) +} + +func testExperimentalArgs(t *testing.T, deploymentFunction createDeploymentStatefulSet) { + //given + dbSecret := &v1.Secret{} + cr := &v1alpha1.Keycloak{ + Spec: v1alpha1.KeycloakSpec{ + KeycloakDeploymentSpec: v1alpha1.KeycloakDeploymentSpec{ + Experimental: v1alpha1.ExperimentalSpec{ + Args: []string{"test"}, + }, + }, + }, + } + + //when + args := deploymentFunction(cr, dbSecret).Spec.Template.Spec.Containers[0].Args + + //then + assert.Equal(t, []string{"test"}, args) +} + +func testExperimentalCommand(t *testing.T, deploymentFunction createDeploymentStatefulSet) { + //given + dbSecret := &v1.Secret{} + cr := &v1alpha1.Keycloak{ + Spec: v1alpha1.KeycloakSpec{ + KeycloakDeploymentSpec: v1alpha1.KeycloakDeploymentSpec{ + Experimental: v1alpha1.ExperimentalSpec{ + Command: []string{"test"}, + }, + }, + }, + } + + //when + command := deploymentFunction(cr, dbSecret).Spec.Template.Spec.Containers[0].Command + + //then + assert.Equal(t, []string{"test"}, command) +} + +func testExperimentalVolumesWithConfigMaps(t *testing.T, deploymentFunction createDeploymentStatefulSet) { + //given + dbSecret := &v1.Secret{} + cr := &v1alpha1.Keycloak{ + Spec: v1alpha1.KeycloakSpec{ + KeycloakDeploymentSpec: v1alpha1.KeycloakDeploymentSpec{ + Experimental: v1alpha1.ExperimentalSpec{ + Volumes: v1alpha1.VolumesSpec{ + Items: []v1alpha1.VolumeSpec{ + { + ConfigMap: &v1alpha1.ConfigMapVolumeSpec{ + Name: "testName", + MountPath: "testMountPath", + Items: []v1.KeyToPath{ + { + Key: "testKey", + Path: "testPath", + }, + }, + }, + }, + }, + DefaultMode: &[]int32{1}[0], + }, + }, + }, + }, + } + + //when + template := deploymentFunction(cr, dbSecret).Spec.Template.Spec + volumeMount := template.Containers[0].VolumeMounts[3] + volume := template.Volumes[3] + + //then + assert.Equal(t, "testName", volumeMount.Name) + assert.Equal(t, "testMountPath", volumeMount.MountPath) + assert.Equal(t, "testName", volume.Name) + assert.Equal(t, "testName", volume.Projected.Sources[0].ConfigMap.Name) + assert.Equal(t, "testKey", volume.Projected.Sources[0].ConfigMap.Items[0].Key) + assert.Equal(t, "testPath", volume.Projected.Sources[0].ConfigMap.Items[0].Path) +} diff --git a/pkg/model/rhsso_deployment.go b/pkg/model/rhsso_deployment.go index 02fb4026f..f504ee2d1 100644 --- a/pkg/model/rhsso_deployment.go +++ b/pkg/model/rhsso_deployment.go @@ -103,6 +103,11 @@ func getRHSSOEnv(cr *v1alpha1.Keycloak, dbSecret *v1.Secret) []v1.EnvVar { }) } + if len(cr.Spec.KeycloakDeploymentSpec.Experimental.Env) > 0 { + // We override Keycloak pre-defined envs with what user specified. Not the other way around. + env = MergeEnvs(cr.Spec.KeycloakDeploymentSpec.Experimental.Env, env) + } + return env } @@ -134,7 +139,7 @@ func RHSSODeployment(cr *v1alpha1.Keycloak, dbSecret *v1.Secret) *v13.StatefulSe }, }, Spec: v1.PodSpec{ - Volumes: KeycloakVolumes(), + Volumes: KeycloakVolumes(cr), InitContainers: KeycloakExtensionsInitContainers(cr), Containers: []v1.Container{ { @@ -161,7 +166,9 @@ func RHSSODeployment(cr *v1alpha1.Keycloak, dbSecret *v1.Secret) *v13.StatefulSe LivenessProbe: livenessProbe(), ReadinessProbe: readinessProbe(), Env: getRHSSOEnv(cr, dbSecret), - VolumeMounts: KeycloakVolumeMounts(RhssoExtensionPath), + Args: cr.Spec.KeycloakDeploymentSpec.Experimental.Args, + Command: cr.Spec.KeycloakDeploymentSpec.Experimental.Command, + VolumeMounts: KeycloakVolumeMounts(cr, RhssoExtensionPath), Resources: getResources(cr), ImagePullPolicy: "Always", }, @@ -183,11 +190,13 @@ func RHSSODeploymentReconciled(cr *v1alpha1.Keycloak, currentState *v13.Stateful reconciled := currentState.DeepCopy() reconciled.ResourceVersion = currentState.ResourceVersion reconciled.Spec.Replicas = SanitizeNumberOfReplicas(cr.Spec.Instances, false) - reconciled.Spec.Template.Spec.Volumes = KeycloakVolumes() + reconciled.Spec.Template.Spec.Volumes = KeycloakVolumes(cr) reconciled.Spec.Template.Spec.Containers = []v1.Container{ { - Name: KeycloakDeploymentName, - Image: Images.Images[RHSSOImage], + Name: KeycloakDeploymentName, + Image: Images.Images[RHSSOImage], + Args: cr.Spec.KeycloakDeploymentSpec.Experimental.Args, + Command: cr.Spec.KeycloakDeploymentSpec.Experimental.Command, Ports: []v1.ContainerPort{ { ContainerPort: KeycloakServicePort, @@ -206,7 +215,7 @@ func RHSSODeploymentReconciled(cr *v1alpha1.Keycloak, currentState *v13.Stateful Protocol: "TCP", }, }, - VolumeMounts: KeycloakVolumeMounts(RhssoExtensionPath), + VolumeMounts: KeycloakVolumeMounts(cr, RhssoExtensionPath), LivenessProbe: livenessProbe(), ReadinessProbe: readinessProbe(), Env: getRHSSOEnv(cr, dbSecret), diff --git a/pkg/model/rhsso_deployment_test.go b/pkg/model/rhsso_deployment_test.go new file mode 100644 index 000000000..585243e8d --- /dev/null +++ b/pkg/model/rhsso_deployment_test.go @@ -0,0 +1,21 @@ +package model + +import ( + "testing" +) + +func TestRHSSODeployment_testExperimentalEnvs(t *testing.T) { + testExperimentalEnvs(t, RHSSODeployment) +} + +func TestRHSSODeployment_testExperimentalArgs(t *testing.T) { + testExperimentalArgs(t, RHSSODeployment) +} + +func TestRHSSODeployment_testExperimentalCommand(t *testing.T) { + testExperimentalCommand(t, RHSSODeployment) +} + +func TestRHSSODeployment_testExperimentalVolumesWithConfigMaps(t *testing.T) { + testExperimentalVolumesWithConfigMaps(t, RHSSODeployment) +} diff --git a/pkg/model/util.go b/pkg/model/util.go index 28ae5b868..bfaabb313 100644 --- a/pkg/model/util.go +++ b/pkg/model/util.go @@ -127,3 +127,21 @@ func GetExternalDatabasePort(secret *v1.Secret) int32 { } return int32(parsed) } + +// This function favors values in "a". +func MergeEnvs(a []v1.EnvVar, b []v1.EnvVar) []v1.EnvVar { + for _, bb := range b { + found := false + for _, aa := range a { + if aa.Name == bb.Name { + aa.Value = bb.Value + found = true + break + } + } + if !found { + a = append(a, bb) + } + } + return a +} diff --git a/pkg/model/util_test.go b/pkg/model/util_test.go index 81676e459..a94e912b0 100644 --- a/pkg/model/util_test.go +++ b/pkg/model/util_test.go @@ -3,6 +3,8 @@ package model import ( "testing" + v1 "k8s.io/api/core/v1" + "github.com/stretchr/testify/assert" ) @@ -30,3 +32,68 @@ func TestIsIP(t *testing.T) { assert.False(t, IsIP([]byte("this.is.a.hostname"))) assert.False(t, IsIP([]byte("http://www.database.url"))) } + +func TestUtil_testMergeEnvs(t *testing.T) { + //given + a := []v1.EnvVar{{ + Name: "a", + Value: "a", + }} + b := []v1.EnvVar{{ + Name: "b", + Value: "b", + }} + + //when + c := MergeEnvs(a, b) + + //then + expected := []v1.EnvVar{ + { + Name: "a", + Value: "a", + }, + { + Name: "b", + Value: "b", + }, + } + assert.Equal(t, expected, c) +} + +func TestUtil_testMergeEnvsWithEmptyArguments(t *testing.T) { + //given + var a []v1.EnvVar + var b []v1.EnvVar + + //when + c := MergeEnvs(a, b) + + //then + var expected []v1.EnvVar + assert.Equal(t, expected, c) +} + +func TestUtil_testMergeEnvsWithDuplicates1(t *testing.T) { + //given + a := []v1.EnvVar{{ + Name: "a", + Value: "a", + }} + b := []v1.EnvVar{{ + Name: "a", + Value: "b", + }} + + //when + c := MergeEnvs(a, b) + + //then + expected := []v1.EnvVar{ + { + Name: "a", + Value: "a", + }, + } + assert.Equal(t, expected, c) +}