parent
b438776b94
commit
cffb8141e2
6 changed files with 165 additions and 14 deletions
|
@ -53,10 +53,7 @@ public class KeycloakIngress extends OperatorManagedResource implements StatusUp
|
|||
var defaultIngress = newIngress();
|
||||
var resultIngress = (existingIngress != null) ? existingIngress : defaultIngress;
|
||||
|
||||
if (resultIngress.getMetadata().getAnnotations() == null) {
|
||||
resultIngress.getMetadata().setAnnotations(new HashMap<>());
|
||||
}
|
||||
resultIngress.getMetadata().getAnnotations().putAll(defaultIngress.getMetadata().getAnnotations());
|
||||
resultIngress.getMetadata().setAnnotations(defaultIngress.getMetadata().getAnnotations());
|
||||
resultIngress.setSpec(defaultIngress.getSpec());
|
||||
return Optional.of(resultIngress);
|
||||
}
|
||||
|
@ -64,15 +61,28 @@ public class KeycloakIngress extends OperatorManagedResource implements StatusUp
|
|||
|
||||
private Ingress newIngress() {
|
||||
var port = KeycloakService.getServicePort(keycloak);
|
||||
var backendProtocol = (!isTlsConfigured(keycloak)) ? "HTTP" : "HTTPS";
|
||||
var tlsTermination = "HTTP".equals(backendProtocol) ? "edge" : "passthrough";
|
||||
var annotations = new HashMap<String, String>();
|
||||
|
||||
// set default annotations
|
||||
if (isTlsConfigured(keycloak)) {
|
||||
annotations.put("nginx.ingress.kubernetes.io/backend-protocol", "HTTPS");
|
||||
annotations.put("route.openshift.io/termination", "passthrough");
|
||||
} else {
|
||||
annotations.put("nginx.ingress.kubernetes.io/backend-protocol", "HTTP");
|
||||
annotations.put("route.openshift.io/termination", "edge");
|
||||
}
|
||||
|
||||
if (keycloak.getSpec().getIngressSpec() != null &&
|
||||
keycloak.getSpec().getIngressSpec().getAnnotations() != null) {
|
||||
annotations.putAll(keycloak.getSpec().getIngressSpec().getAnnotations());
|
||||
|
||||
}
|
||||
|
||||
Ingress ingress = new IngressBuilder()
|
||||
.withNewMetadata()
|
||||
.withName(getName())
|
||||
.withNamespace(getNamespace())
|
||||
.addToAnnotations("nginx.ingress.kubernetes.io/backend-protocol", backendProtocol)
|
||||
.addToAnnotations("route.openshift.io/termination", tlsTermination)
|
||||
.addToAnnotations(annotations)
|
||||
.endMetadata()
|
||||
.withNewSpec()
|
||||
.withNewDefaultBackend()
|
||||
|
|
|
@ -18,14 +18,21 @@
|
|||
package org.keycloak.operator.crds.v2alpha1.deployment.spec;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.annotation.JsonPropertyDescription;
|
||||
import io.sundr.builder.annotations.Buildable;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@Buildable(editableEnabled = false, builderPackage = "io.fabric8.kubernetes.api.builder")
|
||||
public class IngressSpec {
|
||||
|
||||
@JsonProperty("enabled")
|
||||
private boolean enabled = true;
|
||||
|
||||
@JsonProperty("annotations")
|
||||
@JsonPropertyDescription("Additional annotations to be appended to the Ingress object")
|
||||
Map<String, String> annotations;
|
||||
|
||||
public boolean isIngressEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
@ -33,4 +40,12 @@ public class IngressSpec {
|
|||
public void setIngressEnabled(boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
public Map<String, String> getAnnotations() {
|
||||
return annotations;
|
||||
}
|
||||
|
||||
public void setAnnotations(Map<String, String> annotations) {
|
||||
this.annotations = annotations;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -39,6 +39,7 @@ import java.util.Map;
|
|||
import static java.util.concurrent.TimeUnit.MINUTES;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
|
||||
@QuarkusTest
|
||||
public class KeycloakIngressTest extends BaseOperatorTest {
|
||||
|
@ -171,6 +172,7 @@ public class KeycloakIngressTest extends BaseOperatorTest {
|
|||
var kc = K8sUtils.getDefaultKeycloakDeployment();
|
||||
kc.getSpec().setIngressSpec(new IngressSpec());
|
||||
kc.getSpec().getIngressSpec().setIngressEnabled(true);
|
||||
kc.getSpec().getIngressSpec().setAnnotations(Map.of("haproxy.router.openshift.io/disable_cookies", "true"));
|
||||
K8sUtils.deployKeycloak(k8sclient, kc, true);
|
||||
|
||||
var ingress = new KeycloakIngress(k8sclient, kc);
|
||||
|
@ -206,6 +208,7 @@ public class KeycloakIngressTest extends BaseOperatorTest {
|
|||
assertThat(i.getMetadata().getLabels().entrySet().containsAll(labels.entrySet())).isTrue(); // additional labels should not be overwritten
|
||||
assertEquals("HTTPS", i.getMetadata().getAnnotations().get("nginx.ingress.kubernetes.io/backend-protocol"));
|
||||
assertEquals("passthrough", i.getMetadata().getAnnotations().get("route.openshift.io/termination"));
|
||||
assertEquals("true", i.getMetadata().getAnnotations().get("haproxy.router.openshift.io/disable_cookies"));
|
||||
assertEquals(Constants.KEYCLOAK_HTTPS_PORT, i.getSpec().getDefaultBackend().getService().getPort().getNumber());
|
||||
});
|
||||
|
||||
|
@ -267,6 +270,75 @@ public class KeycloakIngressTest extends BaseOperatorTest {
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCustomIngressAnnotations() {
|
||||
var kc = K8sUtils.getDefaultKeycloakDeployment();
|
||||
kc.getSpec().setIngressSpec(new IngressSpec());
|
||||
kc.getSpec().getIngressSpec().setIngressEnabled(true);
|
||||
|
||||
// set 'a'
|
||||
kc.getSpec().getIngressSpec().setAnnotations(Map.of("a", "b"));
|
||||
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("HTTPS", i.getMetadata().getAnnotations().get("nginx.ingress.kubernetes.io/backend-protocol"));
|
||||
assertEquals("passthrough", i.getMetadata().getAnnotations().get("route.openshift.io/termination"));
|
||||
assertEquals("b", i.getMetadata().getAnnotations().get("a"));
|
||||
});
|
||||
|
||||
// update 'a'
|
||||
kc.getSpec().getIngressSpec().setAnnotations(Map.of("a", "bb"));
|
||||
K8sUtils.deployKeycloak(k8sclient, kc, true);
|
||||
|
||||
Awaitility.await()
|
||||
.ignoreExceptions()
|
||||
.untilAsserted(() -> {
|
||||
var i = ingressSelector.get();
|
||||
assertEquals("HTTPS", i.getMetadata().getAnnotations().get("nginx.ingress.kubernetes.io/backend-protocol"));
|
||||
assertEquals("passthrough", i.getMetadata().getAnnotations().get("route.openshift.io/termination"));
|
||||
assertEquals("bb", i.getMetadata().getAnnotations().get("a"));
|
||||
});
|
||||
|
||||
// remove 'a' and add 'c'
|
||||
kc.getSpec().getIngressSpec().setAnnotations(Map.of("c", "d"));
|
||||
K8sUtils.deployKeycloak(k8sclient, kc, true);
|
||||
|
||||
Awaitility.await()
|
||||
.ignoreExceptions()
|
||||
.untilAsserted(() -> {
|
||||
var i = ingressSelector.get();
|
||||
assertEquals("HTTPS", i.getMetadata().getAnnotations().get("nginx.ingress.kubernetes.io/backend-protocol"));
|
||||
assertEquals("passthrough", i.getMetadata().getAnnotations().get("route.openshift.io/termination"));
|
||||
assertFalse(i.getMetadata().getAnnotations().containsKey("a"));
|
||||
assertEquals("d", i.getMetadata().getAnnotations().get("c"));
|
||||
});
|
||||
|
||||
// remove all
|
||||
kc.getSpec().getIngressSpec().setAnnotations(null);
|
||||
K8sUtils.deployKeycloak(k8sclient, kc, true);
|
||||
|
||||
Awaitility.await()
|
||||
.ignoreExceptions()
|
||||
.untilAsserted(() -> {
|
||||
var i = ingressSelector.get();
|
||||
assertEquals("HTTPS", i.getMetadata().getAnnotations().get("nginx.ingress.kubernetes.io/backend-protocol"));
|
||||
assertEquals("passthrough", i.getMetadata().getAnnotations().get("route.openshift.io/termination"));
|
||||
assertFalse(i.getMetadata().getAnnotations().containsKey("a"));
|
||||
assertFalse(i.getMetadata().getAnnotations().containsKey("c"));
|
||||
});
|
||||
}
|
||||
|
||||
private Ingress createCustomIngress(String baseResourceName, String targetNamespace, int portNumber) {
|
||||
|
||||
Ingress customIngressCreated;
|
||||
|
|
|
@ -24,10 +24,11 @@ import org.keycloak.operator.crds.v2alpha1.deployment.Keycloak;
|
|||
import org.keycloak.operator.crds.v2alpha1.deployment.ValueOrSecret;
|
||||
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.TransactionsSpec;
|
||||
import org.keycloak.operator.crds.v2alpha1.deployment.spec.HostnameSpec;
|
||||
import org.keycloak.operator.crds.v2alpha1.deployment.spec.TransactionsSpec;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.hasItem;
|
||||
|
@ -41,6 +42,11 @@ import static org.junit.jupiter.api.Assertions.assertNotNull;
|
|||
|
||||
public class CRSerializationTest {
|
||||
|
||||
private static final Map<String, String> CUSTOM_INGRESS_ANNOTATION = Map.of(
|
||||
"myAnnotation", "myValue",
|
||||
"anotherAnnotation", "anotherValue"
|
||||
);
|
||||
|
||||
@Test
|
||||
public void testDeserialization() {
|
||||
Keycloak keycloak = Serialization.unmarshal(this.getClass().getResourceAsStream("/test-serialization-keycloak-cr.yml"), Keycloak.class);
|
||||
|
@ -49,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(CUSTOM_INGRESS_ANNOTATION, keycloak.getSpec().getIngressSpec().getAnnotations());
|
||||
|
||||
final TransactionsSpec transactionsSpec = keycloak.getSpec().getTransactionsSpec();
|
||||
assertThat(transactionsSpec, notNullValue());
|
||||
|
|
|
@ -18,12 +18,12 @@
|
|||
package org.keycloak.operator.testsuite.unit;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
import io.fabric8.kubernetes.api.model.OwnerReference;
|
||||
import io.fabric8.kubernetes.api.model.OwnerReferenceBuilder;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.keycloak.operator.Constants;
|
||||
import org.keycloak.operator.controllers.KeycloakIngress;
|
||||
import org.keycloak.operator.crds.v2alpha1.deployment.Keycloak;
|
||||
import org.keycloak.operator.crds.v2alpha1.deployment.spec.IngressSpec;
|
||||
|
@ -39,14 +39,19 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
|
|||
|
||||
public class IngressLogicTest {
|
||||
|
||||
private static final String EXISTING_ANNOTATION_KEY = "annotation";
|
||||
|
||||
static class MockKeycloakIngress extends KeycloakIngress {
|
||||
|
||||
private static Keycloak getKeycloak(Boolean defaultIngressEnabled, boolean ingressSpecDefined, boolean tlsConfigured) {
|
||||
private static Keycloak getKeycloak(Boolean defaultIngressEnabled, boolean ingressSpecDefined, boolean tlsConfigured, Map<String, String> annotations) {
|
||||
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 (!tlsConfigured) {
|
||||
kc.getSpec().getHttpSpec().setTlsSecret(null);
|
||||
|
@ -59,14 +64,18 @@ public class IngressLogicTest {
|
|||
}
|
||||
|
||||
public static MockKeycloakIngress build(Boolean defaultIngressEnabled, boolean ingressExists, boolean ingressSpecDefined, boolean tlsConfigured) {
|
||||
return build(defaultIngressEnabled, ingressExists, ingressSpecDefined, tlsConfigured, null);
|
||||
}
|
||||
|
||||
public static MockKeycloakIngress build(Boolean defaultIngressEnabled, boolean ingressExists, boolean ingressSpecDefined, boolean tlsConfigured, Map<String, String> annotations) {
|
||||
MockKeycloakIngress.ingressExists = ingressExists;
|
||||
return new MockKeycloakIngress(defaultIngressEnabled, ingressSpecDefined, tlsConfigured);
|
||||
return new MockKeycloakIngress(defaultIngressEnabled, ingressSpecDefined, tlsConfigured, annotations);
|
||||
}
|
||||
|
||||
public static boolean ingressExists = false;
|
||||
private boolean deleted = false;
|
||||
public MockKeycloakIngress(Boolean defaultIngressEnabled, boolean ingressSpecDefined, boolean tlsConfigured) {
|
||||
super(null, getKeycloak(defaultIngressEnabled, ingressSpecDefined, tlsConfigured));
|
||||
public MockKeycloakIngress(Boolean defaultIngressEnabled, boolean ingressSpecDefined, boolean tlsConfigured, Map<String, String> annotations) {
|
||||
super(null, getKeycloak(defaultIngressEnabled, ingressSpecDefined, tlsConfigured, annotations));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -100,6 +109,7 @@ public class IngressLogicTest {
|
|||
.withName(getName())
|
||||
.withNamespace(cr.getMetadata().getNamespace())
|
||||
.withOwnerReferences(Collections.singletonList(sameCROwnerRef))
|
||||
.withAnnotations(Map.of(EXISTING_ANNOTATION_KEY, "value"))
|
||||
.endMetadata()
|
||||
.build();
|
||||
} else {
|
||||
|
@ -174,4 +184,38 @@ public class IngressLogicTest {
|
|||
assertEquals("HTTP", reconciled.get().getMetadata().getAnnotations().get("nginx.ingress.kubernetes.io/backend-protocol"));
|
||||
assertEquals("edge", reconciled.get().getMetadata().getAnnotations().get("route.openshift.io/termination"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCustomAnnotations() {
|
||||
var kc = MockKeycloakIngress.build(null, false, true, true, Map.of("custom", "value"));
|
||||
Optional<HasMetadata> reconciled = kc.getReconciledResource();
|
||||
assertTrue(reconciled.isPresent());
|
||||
assertFalse(kc.deleted());
|
||||
assertEquals("HTTPS", reconciled.get().getMetadata().getAnnotations().get("nginx.ingress.kubernetes.io/backend-protocol"));
|
||||
assertEquals("passthrough", reconciled.get().getMetadata().getAnnotations().get("route.openshift.io/termination"));
|
||||
assertEquals("value", reconciled.get().getMetadata().getAnnotations().get("custom"));
|
||||
assertFalse(reconciled.get().getMetadata().getAnnotations().containsKey(EXISTING_ANNOTATION_KEY));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRemoveCustomAnnotation() {
|
||||
var kc = MockKeycloakIngress.build(null, true, true, true, null);
|
||||
Optional<HasMetadata> reconciled = kc.getReconciledResource();
|
||||
assertTrue(reconciled.isPresent());
|
||||
assertFalse(kc.deleted());
|
||||
assertEquals("HTTPS", reconciled.get().getMetadata().getAnnotations().get("nginx.ingress.kubernetes.io/backend-protocol"));
|
||||
assertEquals("passthrough", reconciled.get().getMetadata().getAnnotations().get("route.openshift.io/termination"));
|
||||
assertFalse(reconciled.get().getMetadata().getAnnotations().containsKey(EXISTING_ANNOTATION_KEY));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateCustomAnnotation() {
|
||||
var kc = MockKeycloakIngress.build(null, true, true, true, Map.of(EXISTING_ANNOTATION_KEY, "another-value"));
|
||||
Optional<HasMetadata> reconciled = kc.getReconciledResource();
|
||||
assertTrue(reconciled.isPresent());
|
||||
assertFalse(kc.deleted());
|
||||
assertEquals("HTTPS", reconciled.get().getMetadata().getAnnotations().get("nginx.ingress.kubernetes.io/backend-protocol"));
|
||||
assertEquals("passthrough", reconciled.get().getMetadata().getAnnotations().get("route.openshift.io/termination"));
|
||||
assertEquals("another-value", reconciled.get().getMetadata().getAnnotations().get(EXISTING_ANNOTATION_KEY));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,6 +28,9 @@ spec:
|
|||
poolMaxSize: 3
|
||||
ingress:
|
||||
enabled: false
|
||||
annotations:
|
||||
myAnnotation: myValue
|
||||
anotherAnnotation: anotherValue
|
||||
http:
|
||||
httpEnabled: true
|
||||
httpPort: 123
|
||||
|
|
Loading…
Reference in a new issue