Skip to content

Commit 031c22e

Browse files
committed
feat: extract missing CRD exception check to ReconcilerUtils
Fixes #1139
1 parent fd5aee8 commit 031c22e

File tree

4 files changed

+83
-14
lines changed

4 files changed

+83
-14
lines changed

operator-framework-core/src/main/java/io/javaoperatorsdk/operator/ReconcilerUtils.java

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,15 @@
44
import java.io.InputStream;
55
import java.lang.reflect.InvocationTargetException;
66
import java.lang.reflect.Method;
7+
import java.util.Arrays;
78
import java.util.Locale;
9+
import java.util.function.Predicate;
10+
import java.util.regex.Pattern;
11+
import java.util.stream.Collectors;
812

913
import io.fabric8.kubernetes.api.model.HasMetadata;
1014
import io.fabric8.kubernetes.api.model.ObjectMeta;
15+
import io.fabric8.kubernetes.client.KubernetesClientException;
1116
import io.fabric8.kubernetes.client.utils.Serialization;
1217
import io.javaoperatorsdk.operator.api.reconciler.Constants;
1318
import io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration;
@@ -135,4 +140,47 @@ public static <T> T loadYaml(Class<T> clazz, Class loader, String yaml) {
135140
}
136141
}
137142

143+
public static void handleKubernetesClientException(Exception e, String resourceTypeName) {
144+
if (e instanceof MissingCRDException) {
145+
throw ((MissingCRDException) e);
146+
}
147+
148+
if (e instanceof KubernetesClientException) {
149+
KubernetesClientException ke = (KubernetesClientException) e;
150+
if (404 == ke.getCode()) {
151+
// only throw MissingCRDException if the 404 error occurs on the target CRD
152+
if (resourceTypeName.equals(ke.getFullResourceName())
153+
|| matchesResourceType(resourceTypeName, ke)) {
154+
throw new MissingCRDException(resourceTypeName, null, e.getMessage(), e);
155+
}
156+
}
157+
}
158+
}
159+
160+
private static boolean matchesResourceType(String resourceTypeName,
161+
KubernetesClientException exception) {
162+
final var fullResourceName = exception.getFullResourceName();
163+
if (fullResourceName != null) {
164+
return resourceTypeName.equals(fullResourceName);
165+
} else {
166+
// extract matching information from URI in the message if available
167+
final var message = exception.getMessage();
168+
final var regex = Pattern.compile(".*http(s?)://[^/]*/api(s?)/(\\S*).*").matcher(message);
169+
if (regex.matches()) {
170+
var group = regex.group(3);
171+
if (group.endsWith(".")) {
172+
group = group.substring(0, group.length() - 1);
173+
}
174+
final var segments = Arrays.stream(group.split("/")).filter(Predicate.not(String::isEmpty))
175+
.collect(Collectors.toUnmodifiableList());
176+
if (segments.size() != 3) {
177+
return false;
178+
}
179+
final var targetResourceName = segments.get(2) + "." + segments.get(0);
180+
return resourceTypeName.equals(targetResourceName);
181+
}
182+
}
183+
return false;
184+
}
185+
138186
}

operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/controller/ControllerResourceEventSource.java

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,13 @@
88
import io.fabric8.kubernetes.api.model.HasMetadata;
99
import io.fabric8.kubernetes.client.KubernetesClientException;
1010
import io.fabric8.kubernetes.client.informers.ResourceEventHandler;
11-
import io.javaoperatorsdk.operator.MissingCRDException;
1211
import io.javaoperatorsdk.operator.api.config.ControllerConfiguration;
1312
import io.javaoperatorsdk.operator.processing.Controller;
1413
import io.javaoperatorsdk.operator.processing.MDCUtils;
1514
import io.javaoperatorsdk.operator.processing.event.ResourceID;
1615
import io.javaoperatorsdk.operator.processing.event.source.informer.ManagedInformerEventSource;
1716

17+
import static io.javaoperatorsdk.operator.ReconcilerUtils.handleKubernetesClientException;
1818
import static io.javaoperatorsdk.operator.processing.KubernetesResourceUtils.getName;
1919
import static io.javaoperatorsdk.operator.processing.KubernetesResourceUtils.getUID;
2020
import static io.javaoperatorsdk.operator.processing.KubernetesResourceUtils.getVersion;
@@ -50,7 +50,7 @@ public void start() {
5050
try {
5151
super.start();
5252
} catch (KubernetesClientException e) {
53-
handleKubernetesClientException(e);
53+
handleKubernetesClientException(e, controller.getConfiguration().getResourceTypeName());
5454
throw e;
5555
}
5656
}
@@ -90,17 +90,6 @@ public void onDelete(T resource, boolean b) {
9090
eventReceived(ResourceAction.DELETED, resource, null);
9191
}
9292

93-
private void handleKubernetesClientException(Exception e) {
94-
KubernetesClientException ke = (KubernetesClientException) e;
95-
if (404 == ke.getCode()) {
96-
// only throw MissingCRDException if the 404 error occurs on the target CRD
97-
final var targetCRDName = controller.getConfiguration().getResourceTypeName();
98-
if (targetCRDName.equals(ke.getFullResourceName())) {
99-
throw new MissingCRDException(targetCRDName, null, e.getMessage(), e);
100-
}
101-
}
102-
}
103-
10493
@Override
10594
public Optional<T> getSecondaryResource(T primary) {
10695
throw new IllegalStateException("This method should not be called here. Primary: " + primary);

operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/informer/InformerWrapper.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import io.fabric8.kubernetes.client.informers.ResourceEventHandler;
99
import io.fabric8.kubernetes.client.informers.SharedIndexInformer;
1010
import io.javaoperatorsdk.operator.OperatorException;
11+
import io.javaoperatorsdk.operator.ReconcilerUtils;
1112
import io.javaoperatorsdk.operator.processing.LifecycleAware;
1213
import io.javaoperatorsdk.operator.processing.event.ResourceID;
1314
import io.javaoperatorsdk.operator.processing.event.source.ResourceCache;
@@ -25,7 +26,13 @@ public InformerWrapper(SharedIndexInformer<T> informer) {
2526

2627
@Override
2728
public void start() throws OperatorException {
28-
informer.run();
29+
try {
30+
informer.run();
31+
} catch (Exception e) {
32+
ReconcilerUtils.handleKubernetesClientException(e,
33+
HasMetadata.getFullResourceName(informer.getApiTypeClass()));
34+
throw e;
35+
}
2936
}
3037

3138
@Override

operator-framework-core/src/test/java/io/javaoperatorsdk/operator/ReconcilerUtilsTest.java

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,29 @@
22

33
import org.junit.jupiter.api.Test;
44

5+
import io.fabric8.kubernetes.api.model.HasMetadata;
6+
import io.fabric8.kubernetes.api.model.Namespaced;
57
import io.fabric8.kubernetes.api.model.Pod;
68
import io.fabric8.kubernetes.api.model.PodSpec;
79
import io.fabric8.kubernetes.api.model.PodTemplateSpec;
810
import io.fabric8.kubernetes.api.model.apps.Deployment;
911
import io.fabric8.kubernetes.api.model.apps.DeploymentSpec;
12+
import io.fabric8.kubernetes.client.CustomResource;
13+
import io.fabric8.kubernetes.client.KubernetesClientException;
14+
import io.fabric8.kubernetes.model.annotation.Group;
15+
import io.fabric8.kubernetes.model.annotation.ShortNames;
16+
import io.fabric8.kubernetes.model.annotation.Version;
1017
import io.javaoperatorsdk.operator.sample.simple.TestCustomReconciler;
1118
import io.javaoperatorsdk.operator.sample.simple.TestCustomResource;
1219

1320
import static io.javaoperatorsdk.operator.ReconcilerUtils.getDefaultFinalizerName;
1421
import static io.javaoperatorsdk.operator.ReconcilerUtils.getDefaultNameFor;
1522
import static io.javaoperatorsdk.operator.ReconcilerUtils.getDefaultReconcilerName;
23+
import static io.javaoperatorsdk.operator.ReconcilerUtils.handleKubernetesClientException;
1624
import static io.javaoperatorsdk.operator.ReconcilerUtils.isFinalizerValid;
1725
import static org.assertj.core.api.Assertions.assertThat;
1826
import static org.junit.jupiter.api.Assertions.assertEquals;
27+
import static org.junit.jupiter.api.Assertions.assertThrows;
1928
import static org.junit.jupiter.api.Assertions.assertTrue;
2029

2130
class ReconcilerUtilsTest {
@@ -89,4 +98,20 @@ private Deployment createTestDeployment() {
8998
podTemplateSpec.getSpec().setHostname("localhost");
9099
return deployment;
91100
}
101+
102+
@Test
103+
void handleKubernetesExceptionShouldThrowMissingCRDExceptionWhenAppropriate() {
104+
assertThrows(MissingCRDException.class, () -> handleKubernetesClientException(
105+
new KubernetesClientException(
106+
"Failure executing: GET at: https://kubernetes.docker.internal:6443/apis/tomcatoperator.io/v1/tomcats. Message: Not Found.",
107+
404, null),
108+
HasMetadata.getFullResourceName(Tomcat.class)));
109+
}
110+
111+
@Group("tomcatoperator.io")
112+
@Version("v1")
113+
@ShortNames("tc")
114+
private static class Tomcat extends CustomResource<Void, Void> implements Namespaced {
115+
116+
}
92117
}

0 commit comments

Comments
 (0)