Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion common/src/main/java/org/keycloak/common/Profile.java
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,9 @@ public enum Feature {

USER_EVENT_METRICS("Collect metrics based on user events", Type.PREVIEW),

IPA_TUURA_FEDERATION("IPA-Tuura user federation provider", Type.EXPERIMENTAL)
IPA_TUURA_FEDERATION("IPA-Tuura user federation provider", Type.EXPERIMENTAL),

ROLLING_UPDATES("Rolling Updates", Type.PREVIEW),
;

private final Type type;
Expand Down
15 changes: 12 additions & 3 deletions docs/guides/operator/advanced-configuration.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -443,6 +443,12 @@ Check the https://kubernetes.io/docs/concepts/services-networking/network-polici

The Keycloak Operator offers updates strategies to control how the Operator handles changes to the Keycloak CR.

[CAUTION]
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • WARNING RiskType.INTENT_SEMANTIC_CONSISTENCY (confidence 0.70) line (446, 450)
    • 文档警告信息未明确说明rolling-updates功能是永久必需还是仅在预览阶段必需。当前表述'While on preview stage, the feature rolling-updates must be enabled.'存在歧义:1) 未定义'preview stage'的时间范围或结束条件;2) 未说明预览阶段结束后该功能是否仍然必需;3) 用户可能误解为需要永久启用该功能。这违反了文档的清晰性和语义一致性原则。
    • Suggestion: 建议明确说明:1) 'preview stage'的定义和预期结束时间(如特定版本发布后);2) 预览阶段结束后该功能的处理方式(如将成为默认启用、可选或废弃);3) 添加相关链接指向功能状态跟踪或路线图。例如:'During the preview phase (expected until version X.Y), the rolling-updates feature must be enabled. After this phase, the feature will become [default/optional/deprecated].'

====
While on preview stage, the feature `rolling-updates` must be enabled.
Otherwise, the {project_name} Operator will fail.
====

**Supported Updates Types:**

Rolling Updates:: Update the StatefulSet in a rolling fashion, minimizing downtime (requires multiple replicas).
Expand All @@ -466,11 +472,14 @@ kind: Keycloak
metadata:
name: example-kc
spec:
features:
enabled:
- rolling-updates # <1>
update:
strategy: Recreate|<not set> # <1>
strategy: Recreate|<not set> # <2>
----

<1> Set the desired update strategy here (Recreate in this example).
<1> Enable preview feature `rolling-updates`.
<2> Set the desired update strategy here (Recreate in this example).

[%autowidth]
.Possible field values
Expand Down
10 changes: 9 additions & 1 deletion docs/guides/server/update-compatibility.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,11 @@ preview="true"
previewDiscussionLink="https://github.com/keycloak/keycloak/discussions/36785"
>

// TODO Link to discussion?
[CAUTION]
====
While on preview stage, the feature `rolling-updates` must be enabled.
Otherwise, the commands will fail.
====

The goal of this tool is to assist with modifying a {project_name} deployment, whether upgrading to a new version, enabling/disabling features, or changing configuration.
The outcome will indicate whether a rolling upgrade is possible or if a recreate upgrade is required.
Expand Down Expand Up @@ -124,6 +128,10 @@ m|2
m|3
|Rolling Upgrade is not possible.
The deployment must be shut down before applying the new configuration.

m|4
|Rolling Upgrade is not possible.
The feature `rolling-updates` is disabled.
|===

</@tmpl.guide>
2 changes: 1 addition & 1 deletion docs/guides/templates/kc.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,6 @@ bin/kc.[sh|bat] bootstrap-admin ${parameters}
<#macro updatecompatibility parameters>
[source,bash]
----
bin/kc.[sh|bat] update-compatibility ${parameters}
bin/kc.[sh|bat] update-compatibility ${parameters} --features=rolling-updates
----
</#macro>
2 changes: 1 addition & 1 deletion operator/scripts/Dockerfile-custom-image
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@ ARG IMAGE=keycloak
ARG VERSION=latest
FROM $IMAGE:$VERSION

RUN /opt/keycloak/bin/kc.sh build --db=postgres --health-enabled=true
RUN /opt/keycloak/bin/kc.sh build --db=postgres --health-enabled=true --features=rolling-updates
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

package org.keycloak.operator.testsuite.integration;

import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
Expand All @@ -27,10 +28,11 @@
import org.awaitility.Awaitility;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
import org.keycloak.common.Profile;
import org.keycloak.operator.crds.v2alpha1.deployment.Keycloak;
import org.keycloak.operator.crds.v2alpha1.deployment.KeycloakStatusCondition;
import org.keycloak.operator.crds.v2alpha1.deployment.ValueOrSecret;
import org.keycloak.operator.crds.v2alpha1.deployment.spec.UnsupportedSpec;
import org.keycloak.operator.crds.v2alpha1.deployment.spec.FeatureSpec;
import org.keycloak.operator.crds.v2alpha1.deployment.spec.UpdateSpec;
import org.keycloak.operator.upgrade.UpdateStrategy;

Expand Down Expand Up @@ -105,11 +107,12 @@ private static Keycloak createInitialDeployment(UpdateStrategy updateStrategy) {
}
var updateSpec = new UpdateSpec();
updateSpec.setStrategy(updateStrategy);
kc.getSpec().setUpdateSpec(updateSpec);

if (kc.getSpec().getUnsupported() == null) {
kc.getSpec().setUnsupported(new UnsupportedSpec());
if (kc.getSpec().getFeatureSpec() == null) {
kc.getSpec().setFeatureSpec(new FeatureSpec());
}
kc.getSpec().setUpdateSpec(updateSpec);
kc.getSpec().getFeatureSpec().setEnabledFeatures(List.of(Profile.Feature.ROLLING_UPDATES.getKey()));
return kc;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,4 +80,8 @@ void printPreviewWarning() {
printError("Warning! This command is preview and is not recommended for use in production. It may change or be removed at a future release.");
}

void printFeatureDisabled() {
printError("Unable to use this command. The preview feature 'rolling-updates' is not enabled.");
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@
import java.io.File;
import java.io.IOException;

import org.keycloak.common.Profile;
import org.keycloak.quarkus.runtime.cli.PropertyException;
import org.keycloak.quarkus.runtime.compatibility.CompatibilityResult;
import org.keycloak.quarkus.runtime.compatibility.ServerInfo;
import org.keycloak.util.JsonSerialization;
import picocli.CommandLine;
Expand All @@ -41,6 +43,11 @@ public class UpdateCompatibilityCheck extends AbstractUpdatesCommand {

@Override
public void run() {
if (!Profile.isFeatureEnabled(Profile.Feature.ROLLING_UPDATES)) {
printFeatureDisabled();
picocli.exit(CompatibilityResult.FEATURE_DISABLED);
return;
}
printPreviewWarning();
validateConfig();
var info = readServerInfo();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@
import java.io.IOException;

import com.fasterxml.jackson.core.JsonProcessingException;
import org.keycloak.common.Profile;
import org.keycloak.quarkus.runtime.cli.PropertyException;
import org.keycloak.quarkus.runtime.compatibility.CompatibilityResult;
import org.keycloak.quarkus.runtime.compatibility.ServerInfo;
import org.keycloak.util.JsonSerialization;
import picocli.CommandLine;
Expand All @@ -41,6 +43,11 @@ public class UpdateCompatibilityMetadata extends AbstractUpdatesCommand {

@Override
public void run() {
if (!Profile.isFeatureEnabled(Profile.Feature.ROLLING_UPDATES)) {
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • WARNING RiskType.LIFECYCLE_STATE_CONSISTENCY (confidence 0.45) line (46, 50)
    • 命中模式: Lifecycle_State_Consistency-5
      新增的功能检查可能导致在功能关闭时,清理或状态重置逻辑未被执行。
    • Suggestion: 建议检查当滚动更新功能被禁用时,是否需要进行任何状态清理或资源释放操作,确保功能关闭时的状态一致性。

printFeatureDisabled();
picocli.exit(CompatibilityResult.FEATURE_DISABLED);
return;
}
printPreviewWarning();
validateConfig();
var info = compatibilityManager.current();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,11 @@
public interface CompatibilityResult {

int ROLLING_UPGRADE_EXIT_CODE = 0;
int RECREATE_UPGRADE_EXIT_CODE = 4;
// see picocli.CommandLine.ExitCode
// 1 -> software error
// 2 -> usage error
int RECREATE_UPGRADE_EXIT_CODE = 3;
int FEATURE_DISABLED = 4;

/**
* The compatible {@link CompatibilityResult} implementation
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,36 +41,44 @@
@RawDistOnly(reason = "Requires creating JSON file to be available between containers")
public class UpdateCommandDistTest {

private static final String ENABLE_FEATURE = "--features=rolling-updates";

@Test
@Launch({UpdateCompatibility.NAME, UpdateCompatibilityMetadata.NAME})
public void testFeatureNotEnabled(CLIResult cliResult) {
cliResult.assertError("Unable to use this command. The preview feature 'rolling-updates' is not enabled.");
}

@Test
@Launch({UpdateCompatibility.NAME})
public void testMissingSubCommand(CLIResult cliResult) {
cliResult.assertError("Missing required subcommand");
}

@Test
@Launch({UpdateCompatibility.NAME, UpdateCompatibilityMetadata.NAME})
@Launch({UpdateCompatibility.NAME, UpdateCompatibilityMetadata.NAME, ENABLE_FEATURE})
public void testMissingOptionOnSave(CLIResult cliResult) {
cliResult.assertNoMessage("Missing required argument");
}

@Test
@Launch({UpdateCompatibility.NAME, UpdateCompatibilityCheck.NAME})
@Launch({UpdateCompatibility.NAME, UpdateCompatibilityCheck.NAME, ENABLE_FEATURE})
public void testMissingOptionOnCheck(CLIResult cliResult) {
cliResult.assertError("Missing required argument: " + UpdateCompatibilityCheck.INPUT_OPTION_NAME);
}

@Test
public void testCompatible(KeycloakDistribution distribution) throws IOException {
var jsonFile = createTempFile("compatible");
var result = distribution.run(UpdateCompatibility.NAME, UpdateCompatibilityMetadata.NAME, UpdateCompatibilityMetadata.OUTPUT_OPTION_NAME, jsonFile.getAbsolutePath());
var result = distribution.run(UpdateCompatibility.NAME, UpdateCompatibilityMetadata.NAME, UpdateCompatibilityMetadata.OUTPUT_OPTION_NAME, jsonFile.getAbsolutePath(), ENABLE_FEATURE);
result.assertMessage("Metadata:");
assertEquals(0, result.exitCode());

var info = JsonSerialization.mapper.readValue(jsonFile, ServerInfo.class);
assertEquals(Version.VERSION, info.getVersions().get(CompatibilityManagerImpl.KEYCLOAK_VERSION_KEY));
assertEquals(org.infinispan.commons.util.Version.getVersion(), info.getVersions().get(CompatibilityManagerImpl.INFINISPAN_VERSION_KEY));

result = distribution.run(UpdateCompatibility.NAME, UpdateCompatibilityCheck.NAME, UpdateCompatibilityCheck.INPUT_OPTION_NAME, jsonFile.getAbsolutePath());
result = distribution.run(UpdateCompatibility.NAME, UpdateCompatibilityCheck.NAME, UpdateCompatibilityCheck.INPUT_OPTION_NAME, jsonFile.getAbsolutePath(), ENABLE_FEATURE);
result.assertMessage("[OK] Rolling Upgrade is available.");
result.assertNoError("Rolling Upgrade is not available.");
}
Expand All @@ -85,7 +93,7 @@ public void testWrongVersions(KeycloakDistribution distribution) throws IOExcept
CompatibilityManagerImpl.INFINISPAN_VERSION_KEY, org.infinispan.commons.util.Version.getVersion()));
JsonSerialization.mapper.writeValue(jsonFile, info);

var result = distribution.run(UpdateCompatibility.NAME, UpdateCompatibilityCheck.NAME, UpdateCompatibilityCheck.INPUT_OPTION_NAME, jsonFile.getAbsolutePath());
var result = distribution.run(UpdateCompatibility.NAME, UpdateCompatibilityCheck.NAME, UpdateCompatibilityCheck.INPUT_OPTION_NAME, jsonFile.getAbsolutePath(), ENABLE_FEATURE);
result.assertError("[Versions] Rolling Upgrade is not available. 'keycloak' is incompatible: Old=0.0.0.Final, New=%s".formatted(Version.VERSION));

// incompatible infinispan version
Expand All @@ -94,7 +102,7 @@ public void testWrongVersions(KeycloakDistribution distribution) throws IOExcept
CompatibilityManagerImpl.INFINISPAN_VERSION_KEY, "0.0.0.Final"));
JsonSerialization.mapper.writeValue(jsonFile, info);

result = distribution.run(UpdateCompatibility.NAME, UpdateCompatibilityCheck.NAME, UpdateCompatibilityCheck.INPUT_OPTION_NAME, jsonFile.getAbsolutePath());
result = distribution.run(UpdateCompatibility.NAME, UpdateCompatibilityCheck.NAME, UpdateCompatibilityCheck.INPUT_OPTION_NAME, jsonFile.getAbsolutePath(), ENABLE_FEATURE);
result.assertError("[Versions] Rolling Upgrade is not available. 'infinispan' is incompatible: Old=0.0.0.Final, New=%s".formatted(org.infinispan.commons.util.Version.getVersion()));
}

Expand Down