diff --git a/docs/guides/operator/basic-deployment.adoc b/docs/guides/operator/basic-deployment.adoc index 8be9c94314..fc17d981af 100644 --- a/docs/guides/operator/basic-deployment.adoc +++ b/docs/guides/operator/basic-deployment.adoc @@ -176,7 +176,23 @@ CONDITION: RollingUpdate === Accessing the Keycloak deployment -The Keycloak deployment is exposed through a basic Ingress and is accessible through the provided hostname. +The Keycloak deployment is exposed through a basic Ingress and is accessible through the provided hostname. On installations with multiple default IngressClass instances +or when running on OpenShift 4.12+ you should provide an ingressClassName by setting `ingress` spec with `className` property to the desired class name: + +Edit YAML file `example-kc.yaml`: + +[source,yaml] +---- +apiVersion: k8s.keycloak.org/v2alpha1 +kind: Keycloak +metadata: + name: example-kc +spec: + ... + ingress: + className: openshift-default +---- + If the default ingress does not fit your use case, disable it by setting `ingress` spec with `enabled` property to `false` value: Edit YAML file `example-kc.yaml`: diff --git a/operator/src/main/java/org/keycloak/operator/controllers/KeycloakIngress.java b/operator/src/main/java/org/keycloak/operator/controllers/KeycloakIngress.java index 63700572c9..701d2ce655 100644 --- a/operator/src/main/java/org/keycloak/operator/controllers/KeycloakIngress.java +++ b/operator/src/main/java/org/keycloak/operator/controllers/KeycloakIngress.java @@ -72,11 +72,8 @@ public class KeycloakIngress extends OperatorManagedResource implements StatusUp annotations.put("route.openshift.io/termination", "edge"); } - if (keycloak.getSpec().getIngressSpec() != null && - keycloak.getSpec().getIngressSpec().getAnnotations() != null) { - annotations.putAll(keycloak.getSpec().getIngressSpec().getAnnotations()); - - } + var optionalSpec = Optional.ofNullable(keycloak.getSpec().getIngressSpec()); + optionalSpec.map(IngressSpec::getAnnotations).ifPresent(annotations::putAll); Ingress ingress = new IngressBuilder() .withNewMetadata() @@ -85,6 +82,7 @@ public class KeycloakIngress extends OperatorManagedResource implements StatusUp .addToAnnotations(annotations) .endMetadata() .withNewSpec() + .withIngressClassName(optionalSpec.map(IngressSpec::getIngressClassName).orElse(null)) .withNewDefaultBackend() .withNewService() .withName(keycloak.getMetadata().getName() + Constants.KEYCLOAK_SERVICE_SUFFIX) diff --git a/operator/src/main/java/org/keycloak/operator/crds/v2alpha1/deployment/spec/IngressSpec.java b/operator/src/main/java/org/keycloak/operator/crds/v2alpha1/deployment/spec/IngressSpec.java index d885b48151..860237e287 100644 --- a/operator/src/main/java/org/keycloak/operator/crds/v2alpha1/deployment/spec/IngressSpec.java +++ b/operator/src/main/java/org/keycloak/operator/crds/v2alpha1/deployment/spec/IngressSpec.java @@ -27,18 +27,29 @@ import java.util.Map; public class IngressSpec { @JsonProperty("enabled") - private boolean enabled = true; + private boolean ingressEnabled = true; + + @JsonProperty("className") + private String ingressClassName; @JsonProperty("annotations") @JsonPropertyDescription("Additional annotations to be appended to the Ingress object") Map annotations; public boolean isIngressEnabled() { - return enabled; + return ingressEnabled; } public void setIngressEnabled(boolean enabled) { - this.enabled = enabled; + this.ingressEnabled = enabled; + } + + public String getIngressClassName() { + return ingressClassName; + } + + public void setIngressClassName(String className) { + this.ingressClassName = className; } public Map getAnnotations() { diff --git a/operator/src/test/java/org/keycloak/operator/testsuite/integration/KeycloakIngressTest.java b/operator/src/test/java/org/keycloak/operator/testsuite/integration/KeycloakIngressTest.java index eac4bb931b..cd26a5a1d1 100644 --- a/operator/src/test/java/org/keycloak/operator/testsuite/integration/KeycloakIngressTest.java +++ b/operator/src/test/java/org/keycloak/operator/testsuite/integration/KeycloakIngressTest.java @@ -30,6 +30,7 @@ import org.junit.jupiter.api.Test; import org.keycloak.operator.Constants; import org.keycloak.operator.crds.v2alpha1.deployment.Keycloak; import org.keycloak.operator.crds.v2alpha1.deployment.spec.IngressSpec; +import org.keycloak.operator.crds.v2alpha1.deployment.spec.IngressSpecBuilder; import org.keycloak.operator.crds.v2alpha1.deployment.spec.HostnameSpecBuilder; import org.keycloak.operator.testsuite.utils.K8sUtils; import org.keycloak.operator.controllers.KeycloakIngress; @@ -269,6 +270,39 @@ public class KeycloakIngressTest extends BaseOperatorTest { } } } + + @Test + public void testCustomIngressClassName() { + var kc = K8sUtils.getDefaultKeycloakDeployment(); + kc.getSpec().setIngressSpec(new IngressSpecBuilder().withIngressClassName("nginx").build()); + K8sUtils.deployKeycloak(k8sclient, kc, true); + + var ingress = new KeycloakIngress(k8sclient, kc); + var ingressSelector = k8sclient + .network() + .v1() + .ingresses() + .inNamespace(namespace) + .withName(ingress.getName()); + + Awaitility.await() + .ignoreExceptions() + .untilAsserted(() -> { + var i = ingressSelector.get(); + assertEquals("nginx", i.getSpec().getIngressClassName()); + }); + + // update to a different classname + kc.getSpec().setIngressSpec(new IngressSpecBuilder().withIngressClassName("nginx-latest").build()); + K8sUtils.deployKeycloak(k8sclient, kc, true); + + Awaitility.await() + .ignoreExceptions() + .untilAsserted(() -> { + var i = ingressSelector.get(); + assertEquals("nginx-latest", i.getSpec().getIngressClassName()); + }); + } @Test public void testCustomIngressAnnotations() { diff --git a/operator/src/test/java/org/keycloak/operator/testsuite/unit/CRSerializationTest.java b/operator/src/test/java/org/keycloak/operator/testsuite/unit/CRSerializationTest.java index 7f19c6e43c..77fca5ec14 100644 --- a/operator/src/test/java/org/keycloak/operator/testsuite/unit/CRSerializationTest.java +++ b/operator/src/test/java/org/keycloak/operator/testsuite/unit/CRSerializationTest.java @@ -55,6 +55,7 @@ public class CRSerializationTest { assertEquals("my-image", keycloak.getSpec().getImage()); assertEquals("my-tls-secret", keycloak.getSpec().getHttpSpec().getTlsSecret()); assertFalse(keycloak.getSpec().getIngressSpec().isIngressEnabled()); + assertEquals("nginx", keycloak.getSpec().getIngressSpec().getIngressClassName()); assertEquals(CUSTOM_INGRESS_ANNOTATION, keycloak.getSpec().getIngressSpec().getAnnotations()); final TransactionsSpec transactionsSpec = keycloak.getSpec().getTransactionsSpec(); diff --git a/operator/src/test/java/org/keycloak/operator/testsuite/unit/IngressLogicTest.java b/operator/src/test/java/org/keycloak/operator/testsuite/unit/IngressLogicTest.java index 56b9cf4fc9..046d66d61f 100644 --- a/operator/src/test/java/org/keycloak/operator/testsuite/unit/IngressLogicTest.java +++ b/operator/src/test/java/org/keycloak/operator/testsuite/unit/IngressLogicTest.java @@ -27,6 +27,7 @@ import org.junit.jupiter.api.Test; import org.keycloak.operator.controllers.KeycloakIngress; import org.keycloak.operator.crds.v2alpha1.deployment.Keycloak; import org.keycloak.operator.crds.v2alpha1.deployment.spec.IngressSpec; +import org.keycloak.operator.crds.v2alpha1.deployment.spec.IngressSpecBuilder; import org.keycloak.operator.testsuite.utils.K8sUtils; import io.fabric8.kubernetes.api.model.HasMetadata; @@ -35,6 +36,7 @@ import io.fabric8.kubernetes.api.model.networking.v1.IngressBuilder; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; public class IngressLogicTest { @@ -43,15 +45,11 @@ public class IngressLogicTest { static class MockKeycloakIngress extends KeycloakIngress { - private static Keycloak getKeycloak(Boolean defaultIngressEnabled, boolean ingressSpecDefined, boolean tlsConfigured, Map annotations) { + private static Keycloak getKeycloak(boolean tlsConfigured, IngressSpec ingressSpec) { var kc = K8sUtils.getDefaultKeycloakDeployment(); kc.getMetadata().setUid("this-is-a-fake-uid"); - if (ingressSpecDefined) { - kc.getSpec().setIngressSpec(new IngressSpec()); - if (defaultIngressEnabled != null) kc.getSpec().getIngressSpec().setIngressEnabled(defaultIngressEnabled); - if (annotations != null) { - kc.getSpec().getIngressSpec().setAnnotations(annotations); - } + if (ingressSpec != null) { + kc.getSpec().setIngressSpec(ingressSpec); } if (!tlsConfigured) { kc.getSpec().getHttpSpec().setTlsSecret(null); @@ -69,13 +67,23 @@ public class IngressLogicTest { public static MockKeycloakIngress build(Boolean defaultIngressEnabled, boolean ingressExists, boolean ingressSpecDefined, boolean tlsConfigured, Map annotations) { MockKeycloakIngress.ingressExists = ingressExists; - return new MockKeycloakIngress(defaultIngressEnabled, ingressSpecDefined, tlsConfigured, annotations); + IngressSpec ingressSpec = null; + if (ingressSpecDefined) { + ingressSpec = new IngressSpec(); + if (defaultIngressEnabled != null) { + ingressSpec.setIngressEnabled(defaultIngressEnabled); + } + if (annotations != null) { + ingressSpec.setAnnotations(annotations); + } + } + return new MockKeycloakIngress(tlsConfigured, ingressSpec); } public static boolean ingressExists = false; private boolean deleted = false; - public MockKeycloakIngress(Boolean defaultIngressEnabled, boolean ingressSpecDefined, boolean tlsConfigured, Map annotations) { - super(null, getKeycloak(defaultIngressEnabled, ingressSpecDefined, tlsConfigured, annotations)); + public MockKeycloakIngress(boolean tlsConfigured, IngressSpec ingressSpec) { + super(null, getKeycloak(tlsConfigured, ingressSpec)); } @Override @@ -218,4 +226,20 @@ public class IngressLogicTest { assertEquals("passthrough", reconciled.get().getMetadata().getAnnotations().get("route.openshift.io/termination")); assertEquals("another-value", reconciled.get().getMetadata().getAnnotations().get(EXISTING_ANNOTATION_KEY)); } + + @Test + public void testIngressSpecDefinedWithoutClassName() { + var kc = new MockKeycloakIngress(true, new IngressSpec()); + Optional reconciled = kc.getReconciledResource(); + Ingress ingress = reconciled.map(Ingress.class::cast).orElseThrow(); + assertNull(ingress.getSpec().getIngressClassName()); + } + + @Test + public void testIngressSpecDefinedWithClassName() { + var kc = new MockKeycloakIngress(true, new IngressSpecBuilder().withIngressClassName("my-class").build()); + Optional reconciled = kc.getReconciledResource(); + Ingress ingress = reconciled.map(Ingress.class::cast).orElseThrow(); + assertEquals("my-class", ingress.getSpec().getIngressClassName()); + } } diff --git a/operator/src/test/resources/test-serialization-keycloak-cr.yml b/operator/src/test/resources/test-serialization-keycloak-cr.yml index cc2a43810b..af8d8d72a3 100644 --- a/operator/src/test/resources/test-serialization-keycloak-cr.yml +++ b/operator/src/test/resources/test-serialization-keycloak-cr.yml @@ -28,6 +28,7 @@ spec: poolMaxSize: 3 ingress: enabled: false + className: nginx annotations: myAnnotation: myValue anotherAnnotation: anotherValue