diff --git a/docs/documentation/release_notes/topics/24_0_0.adoc b/docs/documentation/release_notes/topics/24_0_0.adoc index 239c677540..7bfe4b6c56 100644 --- a/docs/documentation/release_notes/topics/24_0_0.adoc +++ b/docs/documentation/release_notes/topics/24_0_0.adoc @@ -22,4 +22,10 @@ The endpoint is currently checking availability of the embedded and external Inf This endpoint is not available by default. To enable it, run Keycloak with feature `multi-site`. -Proceed to https://www.keycloak.org/server/features[Enabling and disabling features] guide for more details. \ No newline at end of file +Proceed to https://www.keycloak.org/server/features[Enabling and disabling features] guide for more details. + += Keycloak CR Optimized Field + +The Keycloak CR now includes an `startOptimized` field, which may be used to override the default assumption about whether to use the `--optimized` flag for the start command. +As a result, you can use the CR to configure build time options also when a custom Keycloak image is used. + diff --git a/docs/guides/operator/customizing-keycloak.adoc b/docs/guides/operator/customizing-keycloak.adoc index a3dd31506e..d773b66bab 100644 --- a/docs/guides/operator/customizing-keycloak.adoc +++ b/docs/guides/operator/customizing-keycloak.adoc @@ -45,7 +45,29 @@ spec: [NOTE] ==== -With custom images, every build time option is passed either through a dedicated field or the `additionalOptions` is ignored. +With custom images, every build time option passed either through a dedicated field or the `additionalOptions` is ignored. ==== +=== Non-optimized custom image + +While it is considered a best practice use a pre-augmented image, if you want to use a non-optimized custom image or build time properties with an augmented image that is still possible. You just need set the `startOptimzed` field to `false` as shown in this example: + +[source,yaml] +---- +apiVersion: k8s.keycloak.org/v2alpha1 +kind: Keycloak +metadata: + name: example-kc +spec: + instances: 1 + image: quay.io/my-company/my-keycloak:latest + startOptimized: false + http: + tlsSecret: example-tls-secret + hostname: + hostname: test.keycloak.org +---- + +Keep in mind this will incur the re-augmentation cost on every start. + diff --git a/operator/src/main/java/org/keycloak/operator/Config.java b/operator/src/main/java/org/keycloak/operator/Config.java index 9dcdf70d74..0af58ef508 100644 --- a/operator/src/main/java/org/keycloak/operator/Config.java +++ b/operator/src/main/java/org/keycloak/operator/Config.java @@ -31,6 +31,7 @@ public interface Config { interface Keycloak { String image(); String imagePullPolicy(); + boolean startOptimized(); Map podLabels(); } diff --git a/operator/src/main/java/org/keycloak/operator/controllers/KeycloakDeploymentDependentResource.java b/operator/src/main/java/org/keycloak/operator/controllers/KeycloakDeploymentDependentResource.java index 27be028b80..f8b901933f 100644 --- a/operator/src/main/java/org/keycloak/operator/controllers/KeycloakDeploymentDependentResource.java +++ b/operator/src/main/java/org/keycloak/operator/controllers/KeycloakDeploymentDependentResource.java @@ -208,7 +208,9 @@ public class KeycloakDeploymentDependentResource extends CRUDKubernetesDependent if (Optional.ofNullable(containerBuilder.getArgs()).orElse(List.of()).isEmpty()) { containerBuilder.withArgs("--verbose", "start"); } - if (customImage.isPresent()) { + if (Boolean.TRUE.equals(keycloakCR.getSpec().getStartOptimized()) + || keycloakCR.getSpec().getStartOptimized() == null + && (customImage.isPresent() || operatorConfig.keycloak().startOptimized())) { containerBuilder.addToArgs(OPTIMIZED_ARG); } containerBuilder.addToArgs(0, getJGroupsParameter(keycloakCR)); diff --git a/operator/src/main/java/org/keycloak/operator/crds/v2alpha1/deployment/KeycloakSpec.java b/operator/src/main/java/org/keycloak/operator/crds/v2alpha1/deployment/KeycloakSpec.java index 4ee48714ec..fa998ca140 100644 --- a/operator/src/main/java/org/keycloak/operator/crds/v2alpha1/deployment/KeycloakSpec.java +++ b/operator/src/main/java/org/keycloak/operator/crds/v2alpha1/deployment/KeycloakSpec.java @@ -16,24 +16,24 @@ */ package org.keycloak.operator.crds.v2alpha1.deployment; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonPropertyDescription; - import io.fabric8.kubernetes.api.model.LocalObjectReference; import io.fabric8.kubernetes.model.annotation.SpecReplicas; import org.keycloak.operator.crds.v2alpha1.deployment.spec.DatabaseSpec; import org.keycloak.operator.crds.v2alpha1.deployment.spec.FeatureSpec; +import org.keycloak.operator.crds.v2alpha1.deployment.spec.HostnameSpec; import org.keycloak.operator.crds.v2alpha1.deployment.spec.HttpSpec; -import org.keycloak.operator.crds.v2alpha1.deployment.spec.UnsupportedSpec; import org.keycloak.operator.crds.v2alpha1.deployment.spec.IngressSpec; import org.keycloak.operator.crds.v2alpha1.deployment.spec.TransactionsSpec; -import org.keycloak.operator.crds.v2alpha1.deployment.spec.HostnameSpec; +import org.keycloak.operator.crds.v2alpha1.deployment.spec.UnsupportedSpec; import java.util.ArrayList; import java.util.List; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyDescription; + @JsonInclude(JsonInclude.Include.NON_NULL) public class KeycloakSpec { @@ -44,6 +44,9 @@ public class KeycloakSpec { @JsonPropertyDescription("Custom Keycloak image to be used.") private String image; + @JsonPropertyDescription("Set to force the behavior of the --optimized flag for the start command. If left unspecified the operator will assume custom images have already been augmented.") + private Boolean startOptimized; + @JsonPropertyDescription("Secret(s) that might be used when pulling an image from a private container image registry or repository.") private List imagePullSecrets; @@ -171,4 +174,12 @@ public class KeycloakSpec { public void setAdditionalOptions(List additionalOptions) { this.additionalOptions = additionalOptions; } + + public Boolean getStartOptimized() { + return startOptimized; + } + + public void setStartOptimized(Boolean optimized) { + this.startOptimized = optimized; + } } \ No newline at end of file diff --git a/operator/src/main/resources/application.properties b/operator/src/main/resources/application.properties index cbc85dd43c..5788d5e969 100644 --- a/operator/src/main/resources/application.properties +++ b/operator/src/main/resources/application.properties @@ -7,6 +7,7 @@ quarkus.banner.enabled=false # Operator config operator.keycloak.image=${RELATED_IMAGE_KEYCLOAK:quay.io/keycloak/keycloak:nightly} operator.keycloak.image-pull-policy=Always +operator.keycloak.start-optimized=false # https://quarkus.io/guides/deploying-to-kubernetes#environment-variables-from-keyvalue-pairs quarkus.kubernetes.env.vars.related-image-keycloak=${operator.keycloak.image} diff --git a/operator/src/test/java/org/keycloak/operator/testsuite/unit/PodTemplateTest.java b/operator/src/test/java/org/keycloak/operator/testsuite/unit/PodTemplateTest.java index 888b508013..cb4ae93f96 100644 --- a/operator/src/test/java/org/keycloak/operator/testsuite/unit/PodTemplateTest.java +++ b/operator/src/test/java/org/keycloak/operator/testsuite/unit/PodTemplateTest.java @@ -371,6 +371,35 @@ public class PodTemplateTest { var podTemplate = getDeployment(additionalPodTemplate).getSpec().getTemplate(); // Assert - assertThat(podTemplate.getSpec().getContainers().get(0).getArgs()).doesNotContain("--optimized"); + assertThat(podTemplate.getSpec().getContainers().get(0).getArgs()).doesNotContain(KeycloakDeploymentDependentResource.OPTIMIZED_ARG); } + + @Test + public void testImageNotOptimized() { + // Arrange + PodTemplateSpec additionalPodTemplate = null; + + // Act + var podTemplate = getDeployment(additionalPodTemplate, null, + s -> s.withImage("some-image").withStartOptimized(false)) + .getSpec().getTemplate(); + + // Assert + assertThat(podTemplate.getSpec().getContainers().get(0).getArgs()).doesNotContain(KeycloakDeploymentDependentResource.OPTIMIZED_ARG); + } + + @Test + public void testImageForceOptimized() { + // Arrange + PodTemplateSpec additionalPodTemplate = null; + + // Act + var podTemplate = getDeployment(additionalPodTemplate, null, + s -> s.withStartOptimized(true)) + .getSpec().getTemplate(); + + // Assert + assertThat(podTemplate.getSpec().getContainers().get(0).getCommand().contains(KeycloakDeploymentDependentResource.OPTIMIZED_ARG)); + } + }