Use port name instead of port number for the ingress (#30925)

Also adding a retry if server-side-apply fails

Closes #30924

Signed-off-by: Alexander Schwartz <aschwart@redhat.com>
Co-authored-by: Steven Hawkins <shawkins@redhat.com>
This commit is contained in:
Alexander Schwartz 2024-07-10 19:27:06 +02:00 committed by GitHub
parent a0c99a7ae0
commit bebb314a16
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 38 additions and 4 deletions

View file

@ -18,6 +18,7 @@ package org.keycloak.operator.controllers;
import io.fabric8.kubernetes.api.model.networking.v1.Ingress; import io.fabric8.kubernetes.api.model.networking.v1.Ingress;
import io.fabric8.kubernetes.api.model.networking.v1.IngressBuilder; import io.fabric8.kubernetes.api.model.networking.v1.IngressBuilder;
import io.fabric8.kubernetes.client.KubernetesClientException;
import io.javaoperatorsdk.operator.api.reconciler.Context; import io.javaoperatorsdk.operator.api.reconciler.Context;
import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource; import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource;
import io.javaoperatorsdk.operator.processing.dependent.kubernetes.CRUDKubernetesDependentResource; import io.javaoperatorsdk.operator.processing.dependent.kubernetes.CRUDKubernetesDependentResource;
@ -25,6 +26,7 @@ import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDep
import io.javaoperatorsdk.operator.processing.dependent.workflow.Condition; import io.javaoperatorsdk.operator.processing.dependent.workflow.Condition;
import io.quarkus.logging.Log; import io.quarkus.logging.Log;
import org.jboss.logging.Logger;
import org.keycloak.operator.Constants; import org.keycloak.operator.Constants;
import org.keycloak.operator.Utils; import org.keycloak.operator.Utils;
import org.keycloak.operator.crds.v2alpha1.deployment.Keycloak; import org.keycloak.operator.crds.v2alpha1.deployment.Keycloak;
@ -40,6 +42,8 @@ import static org.keycloak.operator.crds.v2alpha1.CRDUtils.isTlsConfigured;
@KubernetesDependent(labelSelector = Constants.DEFAULT_LABELS_AS_STRING) @KubernetesDependent(labelSelector = Constants.DEFAULT_LABELS_AS_STRING)
public class KeycloakIngressDependentResource extends CRUDKubernetesDependentResource<Ingress, Keycloak> { public class KeycloakIngressDependentResource extends CRUDKubernetesDependentResource<Ingress, Keycloak> {
private static final Logger LOG = Logger.getLogger(KeycloakIngressDependentResource.class.getName());
public static class EnabledCondition implements Condition<Ingress, Keycloak> { public static class EnabledCondition implements Condition<Ingress, Keycloak> {
@Override @Override
public boolean isMet(DependentResource<Ingress, Keycloak> dependentResource, Keycloak primary, public boolean isMet(DependentResource<Ingress, Keycloak> dependentResource, Keycloak primary,
@ -48,6 +52,8 @@ public class KeycloakIngressDependentResource extends CRUDKubernetesDependentRes
} }
} }
private static final ThreadLocal<Boolean> USE_SSA = new ThreadLocal<>();
public KeycloakIngressDependentResource() { public KeycloakIngressDependentResource() {
super(Ingress.class); super(Ingress.class);
} }
@ -56,11 +62,39 @@ public class KeycloakIngressDependentResource extends CRUDKubernetesDependentRes
return Optional.ofNullable(keycloak.getSpec().getIngressSpec()).map(IngressSpec::isIngressEnabled).orElse(true); return Optional.ofNullable(keycloak.getSpec().getIngressSpec()).map(IngressSpec::isIngressEnabled).orElse(true);
} }
@Override
public Ingress update(Ingress actual, Ingress desired, Keycloak primary, Context<Keycloak> context) {
try {
return super.update(actual, desired, primary, context);
} catch (KubernetesClientException e) {
if (e.getCode() == 422) {
// This attempts to recover from errors like "Failure executing: PATCH" / "Invalid value: cannot set both port name & port number."
// when the server side apply fails.
try {
LOG.info("Failed to update Ingress with server-side apply, retrying with client-side", e);
USE_SSA.set(false);
return super.update(actual, desired, primary, context);
} finally {
USE_SSA.remove();
}
}
throw e;
}
}
@Override
protected boolean useSSA(Context<Keycloak> context) {
if (Boolean.FALSE.equals(USE_SSA.get())) {
return false;
}
return super.useSSA(context);
}
@Override @Override
public Ingress desired(Keycloak keycloak, Context<Keycloak> context) { public Ingress desired(Keycloak keycloak, Context<Keycloak> context) {
var annotations = new HashMap<String, String>(); var annotations = new HashMap<String, String>();
boolean tlsConfigured = isTlsConfigured(keycloak); boolean tlsConfigured = isTlsConfigured(keycloak);
var port = KeycloakServiceDependentResource.getServicePort(tlsConfigured, keycloak); var portName = tlsConfigured ? Constants.KEYCLOAK_HTTPS_PORT_NAME : Constants.KEYCLOAK_HTTP_PORT_NAME;
if (tlsConfigured) { if (tlsConfigured) {
annotations.put("nginx.ingress.kubernetes.io/backend-protocol", "HTTPS"); annotations.put("nginx.ingress.kubernetes.io/backend-protocol", "HTTPS");
@ -86,7 +120,7 @@ public class KeycloakIngressDependentResource extends CRUDKubernetesDependentRes
.withNewService() .withNewService()
.withName(KeycloakServiceDependentResource.getServiceName(keycloak)) .withName(KeycloakServiceDependentResource.getServiceName(keycloak))
.withNewPort() .withNewPort()
.withNumber(port) .withName(portName)
.endPort() .endPort()
.endService() .endService()
.endDefaultBackend() .endDefaultBackend()
@ -98,7 +132,7 @@ public class KeycloakIngressDependentResource extends CRUDKubernetesDependentRes
.withNewService() .withNewService()
.withName(KeycloakServiceDependentResource.getServiceName(keycloak)) .withName(KeycloakServiceDependentResource.getServiceName(keycloak))
.withNewPort() .withNewPort()
.withNumber(port) .withName(portName)
.endPort() .endPort()
.endService() .endService()
.endBackend() .endBackend()

View file

@ -238,7 +238,7 @@ public class KeycloakIngressTest extends BaseOperatorTest {
assertEquals("HTTPS", i.getMetadata().getAnnotations().get("nginx.ingress.kubernetes.io/backend-protocol")); assertEquals("HTTPS", i.getMetadata().getAnnotations().get("nginx.ingress.kubernetes.io/backend-protocol"));
assertEquals("passthrough", i.getMetadata().getAnnotations().get("route.openshift.io/termination")); assertEquals("passthrough", i.getMetadata().getAnnotations().get("route.openshift.io/termination"));
assertEquals("true", i.getMetadata().getAnnotations().get("haproxy.router.openshift.io/disable_cookies")); assertEquals("true", i.getMetadata().getAnnotations().get("haproxy.router.openshift.io/disable_cookies"));
assertEquals(Constants.KEYCLOAK_HTTPS_PORT, i.getSpec().getDefaultBackend().getService().getPort().getNumber()); assertEquals(Constants.KEYCLOAK_HTTPS_PORT_NAME, i.getSpec().getDefaultBackend().getService().getPort().getName());
}); });
// Delete the ingress // Delete the ingress