diff --git a/docs/documentation/release_notes/topics/22_0_0.adoc b/docs/documentation/release_notes/topics/22_0_0.adoc index ba1250e7e6..f388f9f226 100644 --- a/docs/documentation/release_notes/topics/22_0_0.adoc +++ b/docs/documentation/release_notes/topics/22_0_0.adoc @@ -86,6 +86,10 @@ The previous and now removed WildFly distribution provided a built-in vault prov In relation to the KeyStore Vault news, we also integrated Quarkus's recently released feature called KeyStore Config Source. This means that among the already existing configuration sources (CLI parameters, environment variables and files), you can now configure your Keycloak server via configuration properties stored in a Java keystore file. You can learn more about this feature in the https://www.keycloak.org/server/configuration[Configuration guide]. += k8s.keycloak.org/v2alpha1 changes + +The are additional fields available in the keycloak.status to facilitate keycloak being a scalable resource. There are also additional fields that make the status easier to interpret such as observedGeneration and condition observedGeneration and lastTransitionTime fields. However the condition status field was also changed from a boolean to a string for conformance with standard Kubernetes conditions. Please make sure any of your usage of this field is updated to expect the values "True", "False", or "Unknown", rather than true or false. + = Account Console v3 promoted to preview In version 21.1.0 of Keycloak the new Account Console (version 3) was introduced as an experimental feature. Starting this version it has been promoted to a preview feature. diff --git a/operator/scripts/check-examples-installed.sh b/operator/scripts/check-examples-installed.sh index de8d1f18a5..96f6ed3820 100755 --- a/operator/scripts/check-examples-installed.sh +++ b/operator/scripts/check-examples-installed.sh @@ -3,7 +3,7 @@ set -euxo pipefail max_retries=500 c=0 -while [[ $(kubectl get keycloaks/example-kc -o jsonpath="{.status.conditions[?(@.type == 'Ready')].status}") != "true" ]] +while [[ $(kubectl get keycloaks/example-kc -o jsonpath="{.status.conditions[?(@.type == 'Ready')].status}") != "True" ]] do echo "waiting for Keycloak example-kc status" ((c++)) && ((c==max_retries)) && exit -1 @@ -11,7 +11,7 @@ do done c=0 -while [[ $(kubectl get keycloakrealmimports/example-count0-kc -o jsonpath="{.status.conditions[?(@.type == 'Done')].status}") != "true" ]] +while [[ $(kubectl get keycloakrealmimports/example-count0-kc -o jsonpath="{.status.conditions[?(@.type == 'Done')].status}") != "True" ]] do echo "waiting for Keycloak Realm Import example-count0-kc status" ((c++)) && ((c==max_retries)) && exit -1 diff --git a/operator/src/main/java/org/keycloak/operator/controllers/KeycloakRealmImportController.java b/operator/src/main/java/org/keycloak/operator/controllers/KeycloakRealmImportController.java index cd49153d15..41ae1346f7 100644 --- a/operator/src/main/java/org/keycloak/operator/controllers/KeycloakRealmImportController.java +++ b/operator/src/main/java/org/keycloak/operator/controllers/KeycloakRealmImportController.java @@ -96,7 +96,7 @@ public class KeycloakRealmImportController implements Reconciler c.getType().equals(KeycloakRealmImportStatusCondition.DONE) && !c.getStatus())) { + .anyMatch(c -> c.getType().equals(KeycloakRealmImportStatusCondition.DONE) && !Boolean.TRUE.equals(c.getStatus()))) { updateControl.rescheduleAfter(10, TimeUnit.SECONDS); } diff --git a/operator/src/main/java/org/keycloak/operator/crds/v2alpha1/StatusCondition.java b/operator/src/main/java/org/keycloak/operator/crds/v2alpha1/StatusCondition.java new file mode 100644 index 0000000000..313a7ebc65 --- /dev/null +++ b/operator/src/main/java/org/keycloak/operator/crds/v2alpha1/StatusCondition.java @@ -0,0 +1,138 @@ +/* + * Copyright 2022 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.keycloak.operator.crds.v2alpha1; + +import java.util.Objects; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * @author Vaclav Muzikar + */ +@JsonInclude(JsonInclude.Include.NON_NULL) +public class StatusCondition { + public enum Status { + True, + False, + Unknown + } + + private String type; + private String status = Status.Unknown.name(); + private String message; + private String lastTransitionTime; + private Long observedGeneration; + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + @JsonIgnore + public Boolean getStatus() { + if (status == null) { + return null; + } + // account for the legacy boolean string as well + switch (status) { + case "false": + case "False": + return false; + case "true": + case "True": + return true; + default: + return null; + } + } + + @JsonProperty("status") + public String getStatusString() { + return status; + } + + @JsonProperty("status") + public void setStatusString(String status) { + this.status = status; + } + + @JsonIgnore + public void setStatus(Boolean status) { + if (status == null) { + this.status = Status.Unknown.name(); + } else if (status) { + this.status = Status.True.name(); + } else { + this.status = Status.False.name(); + } + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public String getLastTransitionTime() { + return lastTransitionTime; + } + + public void setLastTransitionTime(String lastTransitionTime) { + this.lastTransitionTime = lastTransitionTime; + } + + public Long getObservedGeneration() { + return observedGeneration; + } + + public void setObservedGeneration(Long observedGeneration) { + this.observedGeneration = observedGeneration; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + StatusCondition that = (StatusCondition) o; + return Objects.equals(getType(), that.getType()) && Objects.equals(getStatus(), that.getStatus()) && Objects.equals(getMessage(), that.getMessage()) + && Objects.equals(getLastTransitionTime(), that.getLastTransitionTime()) + && Objects.equals(getObservedGeneration(), that.getObservedGeneration()); + } + + @Override + public int hashCode() { + return Objects.hash(getType(), getStatus(), getMessage(), getObservedGeneration(), getLastTransitionTime()); + } + + @Override + public String toString() { + return getClass().getSimpleName() + "{" + + "type='" + type + '\'' + + ", status=" + status + + ", message='" + message + '\'' + + '}'; + } + +} diff --git a/operator/src/main/java/org/keycloak/operator/crds/v2alpha1/deployment/KeycloakStatusCondition.java b/operator/src/main/java/org/keycloak/operator/crds/v2alpha1/deployment/KeycloakStatusCondition.java index 0196a00478..3cd58a101d 100644 --- a/operator/src/main/java/org/keycloak/operator/crds/v2alpha1/deployment/KeycloakStatusCondition.java +++ b/operator/src/main/java/org/keycloak/operator/crds/v2alpha1/deployment/KeycloakStatusCondition.java @@ -17,84 +17,13 @@ package org.keycloak.operator.crds.v2alpha1.deployment; -import java.util.Objects; +import org.keycloak.operator.crds.v2alpha1.StatusCondition; /** * @author Vaclav Muzikar */ -public class KeycloakStatusCondition { +public class KeycloakStatusCondition extends StatusCondition { public static final String READY = "Ready"; public static final String HAS_ERRORS = "HasErrors"; public static final String ROLLING_UPDATE = "RollingUpdate"; - - // string to avoid enums in CRDs - private String type; - private Boolean status; - private String message; - private String lastTransitionTime; - private Long observedGeneration; - - public String getType() { - return type; - } - - public void setType(String type) { - this.type = type; - } - - public Boolean getStatus() { - return status; - } - - public void setStatus(Boolean status) { - this.status = status; - } - - public String getMessage() { - return message; - } - - public void setMessage(String message) { - this.message = message; - } - - public String getLastTransitionTime() { - return lastTransitionTime; - } - - public void setLastTransitionTime(String lastTransitionTime) { - this.lastTransitionTime = lastTransitionTime; - } - - public Long getObservedGeneration() { - return observedGeneration; - } - - public void setObservedGeneration(Long observedGeneration) { - this.observedGeneration = observedGeneration; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - KeycloakStatusCondition that = (KeycloakStatusCondition) o; - return Objects.equals(getType(), that.getType()) && Objects.equals(getStatus(), that.getStatus()) && Objects.equals(getMessage(), that.getMessage()) - && Objects.equals(getLastTransitionTime(), that.getLastTransitionTime()) - && Objects.equals(getObservedGeneration(), that.getObservedGeneration()); - } - - @Override - public int hashCode() { - return Objects.hash(getType(), getStatus(), getMessage(), getObservedGeneration(), getLastTransitionTime()); - } - - @Override - public String toString() { - return "KeycloakStatusCondition{" + - "type='" + type + '\'' + - ", status=" + status + - ", message='" + message + '\'' + - '}'; - } } diff --git a/operator/src/main/java/org/keycloak/operator/crds/v2alpha1/realmimport/KeycloakRealmImportStatus.java b/operator/src/main/java/org/keycloak/operator/crds/v2alpha1/realmimport/KeycloakRealmImportStatus.java index c0c97d07e8..521c1d39e1 100644 --- a/operator/src/main/java/org/keycloak/operator/crds/v2alpha1/realmimport/KeycloakRealmImportStatus.java +++ b/operator/src/main/java/org/keycloak/operator/crds/v2alpha1/realmimport/KeycloakRealmImportStatus.java @@ -38,7 +38,7 @@ public class KeycloakRealmImportStatus { public boolean isDone() { return conditions .stream() - .anyMatch(c -> c.getStatus() && c.getType().equals(DONE)); + .anyMatch(c -> Boolean.TRUE.equals(c.getStatus()) && c.getType().equals(DONE)); } @Override diff --git a/operator/src/main/java/org/keycloak/operator/crds/v2alpha1/realmimport/KeycloakRealmImportStatusCondition.java b/operator/src/main/java/org/keycloak/operator/crds/v2alpha1/realmimport/KeycloakRealmImportStatusCondition.java index f3a11b1e19..7192cf5641 100644 --- a/operator/src/main/java/org/keycloak/operator/crds/v2alpha1/realmimport/KeycloakRealmImportStatusCondition.java +++ b/operator/src/main/java/org/keycloak/operator/crds/v2alpha1/realmimport/KeycloakRealmImportStatusCondition.java @@ -17,61 +17,10 @@ package org.keycloak.operator.crds.v2alpha1.realmimport; -import java.util.Objects; +import org.keycloak.operator.crds.v2alpha1.StatusCondition; -public class KeycloakRealmImportStatusCondition { +public class KeycloakRealmImportStatusCondition extends StatusCondition { public static final String DONE = "Done"; public static final String STARTED = "Started"; public static final String HAS_ERRORS = "HasErrors"; - - // string to avoid enums in CRDs - private String type; - private Boolean status; - private String message; - - public String getType() { - return type; - } - - public void setType(String type) { - this.type = type; - } - - public Boolean getStatus() { - return status; - } - - public void setStatus(Boolean status) { - this.status = status; - } - - public String getMessage() { - return message; - } - - public void setMessage(String message) { - this.message = message; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - KeycloakRealmImportStatusCondition that = (KeycloakRealmImportStatusCondition) o; - return getType() == that.getType() && Objects.equals(getStatus(), that.getStatus()) && Objects.equals(getMessage(), that.getMessage()); - } - - @Override - public int hashCode() { - return Objects.hash(getType(), getStatus(), getMessage()); - } - - @Override - public String toString() { - return "KeycloakRealmImportStatusCondition{" + - "type='" + type + '\'' + - ", status=" + status + - ", message='" + message + '\'' + - '}'; - } } diff --git a/operator/src/test/java/org/keycloak/operator/testsuite/unit/KeycloakStatusTest.java b/operator/src/test/java/org/keycloak/operator/testsuite/unit/KeycloakStatusTest.java index e83545ee71..d11763cdab 100644 --- a/operator/src/test/java/org/keycloak/operator/testsuite/unit/KeycloakStatusTest.java +++ b/operator/src/test/java/org/keycloak/operator/testsuite/unit/KeycloakStatusTest.java @@ -17,6 +17,8 @@ package org.keycloak.operator.testsuite.unit; +import io.fabric8.kubernetes.client.utils.Serialization; + import org.assertj.core.api.Condition; import org.junit.jupiter.api.Test; import org.keycloak.operator.crds.v2alpha1.deployment.KeycloakStatus; @@ -25,6 +27,7 @@ import org.keycloak.operator.crds.v2alpha1.deployment.KeycloakStatusCondition; import org.keycloak.operator.testsuite.utils.CRAssert; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotEquals; public class KeycloakStatusTest { @@ -137,4 +140,16 @@ public class KeycloakStatusTest { assertEquals(3, status.getInstances()); } + @Test + public void testStatusSerializtion() { + KeycloakStatusCondition condition = new KeycloakStatusCondition(); + condition.setStatus(false); + + String yaml = Serialization.asYaml(condition); + assertEquals("---\nstatus: \"False\"\n", yaml); + + var deserialized = Serialization.unmarshal(yaml, KeycloakStatusCondition.class); + assertFalse(deserialized.getStatus()); + } + } diff --git a/operator/src/test/java/org/keycloak/operator/testsuite/utils/CRAssert.java b/operator/src/test/java/org/keycloak/operator/testsuite/utils/CRAssert.java index 0c9eb34edc..6fd29c5c94 100644 --- a/operator/src/test/java/org/keycloak/operator/testsuite/utils/CRAssert.java +++ b/operator/src/test/java/org/keycloak/operator/testsuite/utils/CRAssert.java @@ -26,6 +26,8 @@ import org.keycloak.operator.crds.v2alpha1.deployment.KeycloakStatus; import org.keycloak.operator.crds.v2alpha1.deployment.KeycloakStatusCondition; import org.keycloak.operator.crds.v2alpha1.realmimport.KeycloakRealmImport; +import java.util.Objects; + import static org.assertj.core.api.Assertions.assertThat; /** @@ -70,8 +72,8 @@ public final class CRAssert { .noneMatch(c -> c.getMessage().contains(message)); } - public static void assertKeycloakRealmImportStatusCondition(KeycloakRealmImport kri, String condition, boolean status) { + public static void assertKeycloakRealmImportStatusCondition(KeycloakRealmImport kri, String condition, Boolean status) { assertThat(kri.getStatus().getConditions()) - .anyMatch(c -> c.getType().equals(condition) && c.getStatus() == status); + .anyMatch(c -> c.getType().equals(condition) && Objects.equals(c.getStatus(), status)); } }