Skip to content

Commit eb46a0b

Browse files
committed
fix: add scope validation on custom resource
Fixes operator-framework#339
1 parent b3d76cb commit eb46a0b

File tree

5 files changed

+106
-4
lines changed

5 files changed

+106
-4
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package io.javaoperatorsdk.operator;
2+
3+
import io.fabric8.kubernetes.api.model.Cluster;
4+
import io.fabric8.kubernetes.api.model.Namespaced;
5+
import io.fabric8.kubernetes.api.model.apiextensions.v1.CustomResourceDefinition;
6+
import java.util.Arrays;
7+
8+
public abstract class CustomResourceUtils {
9+
10+
/**
11+
* Applies internal validations that may not be handled by the fabric8 client.
12+
*
13+
* @param resClass Custom Resource to validate
14+
* @param crd CRD for the Custom Resource
15+
* @throws OperatorException when the Custom Resource has validation error
16+
*/
17+
public static void assertCustomResource(Class<?> resClass, CustomResourceDefinition crd) {
18+
var namespaced =
19+
Arrays.stream(resClass.getInterfaces())
20+
.filter(classInterface -> classInterface.equals(Namespaced.class))
21+
.findAny()
22+
.isPresent();
23+
24+
if (!namespaced && Namespaced.class.getSimpleName().equals(crd.getSpec().getScope())) {
25+
throw new OperatorException(
26+
"Custom resource '"
27+
+ resClass.getName()
28+
+ "' must implement '"
29+
+ Namespaced.class.getName()
30+
+ "' since CRD '"
31+
+ crd.getMetadata().getName()
32+
+ "' is scoped as 'Namespaced'");
33+
} else if (namespaced && Cluster.class.getSimpleName().equals(crd.getSpec().getScope())) {
34+
throw new OperatorException(
35+
"Custom resource '"
36+
+ resClass.getName()
37+
+ "' must not implement '"
38+
+ Namespaced.class.getName()
39+
+ "' since CRD '"
40+
+ crd.getMetadata().getName()
41+
+ "' is scoped as 'Cluster'");
42+
}
43+
}
44+
}

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

+8-4
Original file line numberDiff line numberDiff line change
@@ -95,10 +95,6 @@ public <R extends CustomResource> void register(
9595
final var targetNamespaces = configuration.getNamespaces().toArray(new String[] {});
9696
Class<R> resClass = configuration.getCustomResourceClass();
9797
String finalizer = configuration.getFinalizer();
98-
final var client = k8sClient.customResources(resClass);
99-
EventDispatcher dispatcher =
100-
new EventDispatcher(
101-
controller, finalizer, new EventDispatcher.CustomResourceFacade(client));
10298

10399
// check that the custom resource is known by the cluster
104100
final var crdName = configuration.getCRDName();
@@ -114,6 +110,14 @@ public <R extends CustomResource> void register(
114110
+ " cannot be registered");
115111
}
116112

113+
// Apply validations that are not handled by fabric8
114+
CustomResourceUtils.assertCustomResource(resClass, crd);
115+
116+
final var client = k8sClient.customResources(resClass);
117+
EventDispatcher dispatcher =
118+
new EventDispatcher(
119+
controller, finalizer, new EventDispatcher.CustomResourceFacade(client));
120+
117121
CustomResourceCache customResourceCache = new CustomResourceCache();
118122
DefaultEventHandler defaultEventHandler =
119123
new DefaultEventHandler(customResourceCache, dispatcher, controllerName, retry);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package io.javaoperatorsdk.operator;
2+
3+
import io.javaoperatorsdk.operator.sample.simple.NamespacedTestCustomResource;
4+
import io.javaoperatorsdk.operator.sample.simple.TestCustomResource;
5+
import org.junit.jupiter.api.Assertions;
6+
import org.junit.jupiter.api.Test;
7+
8+
public class CustomResourceUtilsTest {
9+
@Test
10+
public void assertNamespacedCustomResource() {
11+
var clusterCrd = TestUtils.testCRD("Cluster");
12+
13+
CustomResourceUtils.assertCustomResource(TestCustomResource.class, clusterCrd);
14+
15+
Assertions.assertThrows(
16+
OperatorException.class,
17+
() ->
18+
CustomResourceUtils.assertCustomResource(
19+
NamespacedTestCustomResource.class, clusterCrd));
20+
21+
var namespacedCrd = TestUtils.testCRD("Namespaced");
22+
23+
Assertions.assertThrows(
24+
OperatorException.class,
25+
() -> CustomResourceUtils.assertCustomResource(TestCustomResource.class, namespacedCrd));
26+
27+
CustomResourceUtils.assertCustomResource(NamespacedTestCustomResource.class, namespacedCrd);
28+
}
29+
}

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

+13
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package io.javaoperatorsdk.operator;
22

33
import io.fabric8.kubernetes.api.model.ObjectMetaBuilder;
4+
import io.fabric8.kubernetes.api.model.apiextensions.v1.CustomResourceDefinition;
5+
import io.fabric8.kubernetes.api.model.apiextensions.v1.CustomResourceDefinitionBuilder;
46
import io.javaoperatorsdk.operator.sample.simple.TestCustomResource;
57
import io.javaoperatorsdk.operator.sample.simple.TestCustomResourceSpec;
68
import java.util.HashMap;
@@ -15,6 +17,17 @@ public static TestCustomResource testCustomResource() {
1517
return testCustomResource(UUID.randomUUID().toString());
1618
}
1719

20+
public static CustomResourceDefinition testCRD(String scope) {
21+
return new CustomResourceDefinitionBuilder()
22+
.editOrNewSpec()
23+
.withScope(scope)
24+
.and()
25+
.editOrNewMetadata()
26+
.withName("test.operator.javaoperatorsdk.io")
27+
.and()
28+
.build();
29+
}
30+
1831
public static TestCustomResource testCustomResource(String uid) {
1932
TestCustomResource resource = new TestCustomResource();
2033
resource.setMetadata(
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package io.javaoperatorsdk.operator.sample.simple;
2+
3+
import io.fabric8.kubernetes.api.model.Namespaced;
4+
import io.fabric8.kubernetes.client.CustomResource;
5+
import io.fabric8.kubernetes.model.annotation.Group;
6+
import io.fabric8.kubernetes.model.annotation.Version;
7+
8+
@Group("namespaced-sample.javaoperatorsdk.io")
9+
@Version("v1")
10+
public class NamespacedTestCustomResource
11+
extends CustomResource<TestCustomResourceSpec, TestCustomResourceStatus>
12+
implements Namespaced {}

0 commit comments

Comments
 (0)