Skip to content

Commit 4f7daa4

Browse files
authored
feat: throw exception if desired is null (#1180)
1 parent fb5d975 commit 4f7daa4

File tree

3 files changed

+167
-0
lines changed

3 files changed

+167
-0
lines changed

operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/AbstractDependentResource.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ public ReconcileResult<R> reconcile(P primary, Context<P> context) {
3333
if (maybeActual.isEmpty()) {
3434
if (creatable) {
3535
var desired = desired(primary, context);
36+
throwIfNull(desired, primary, "Desired");
3637
logForOperation("Creating", primary, desired);
3738
var createdResource = handleCreate(desired, primary, context);
3839
return ReconcileResult.resourceCreated(createdResource);
@@ -43,6 +44,7 @@ public ReconcileResult<R> reconcile(P primary, Context<P> context) {
4344
final var match = updater.match(actual, primary, context);
4445
if (!match.matched()) {
4546
final var desired = match.computedDesired().orElse(desired(primary, context));
47+
throwIfNull(desired, primary, "Desired");
4648
logForOperation("Updating", primary, desired);
4749
var updatedResource = handleUpdate(actual, desired, primary, context);
4850
return ReconcileResult.resourceUpdated(updatedResource);
@@ -59,6 +61,13 @@ public ReconcileResult<R> reconcile(P primary, Context<P> context) {
5961
return ReconcileResult.noOperation(maybeActual.orElse(null));
6062
}
6163

64+
private void throwIfNull(R desired, P primary, String descriptor) {
65+
if (desired == null) {
66+
throw new DependentResourceException(
67+
descriptor + " cannot be null. Primary ID: " + ResourceID.fromResource(primary));
68+
}
69+
}
70+
6271
private void logForOperation(String operation, P primary, R desired) {
6372
final var desiredDesc = desired instanceof HasMetadata
6473
? "'" + ((HasMetadata) desired).getMetadata().getName() + "' "
@@ -71,6 +80,7 @@ private void logForOperation(String operation, P primary, R desired) {
7180
protected R handleCreate(R desired, P primary, Context<P> context) {
7281
ResourceID resourceID = ResourceID.fromResource(primary);
7382
R created = creator.create(desired, primary, context);
83+
throwIfNull(created, primary, "Created resource");
7484
onCreated(resourceID, created);
7585
return created;
7686
}
@@ -98,6 +108,7 @@ protected R handleCreate(R desired, P primary, Context<P> context) {
98108
protected R handleUpdate(R actual, R desired, P primary, Context<P> context) {
99109
ResourceID resourceID = ResourceID.fromResource(primary);
100110
R updated = updater.update(actual, desired, primary, context);
111+
throwIfNull(updated, primary, "Updated resource");
101112
onUpdated(resourceID, updated, actual);
102113
return updated;
103114
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package io.javaoperatorsdk.operator.processing.dependent;
2+
3+
import io.javaoperatorsdk.operator.OperatorException;
4+
5+
public class DependentResourceException extends OperatorException {
6+
7+
public DependentResourceException() {}
8+
9+
public DependentResourceException(String message) {
10+
super(message);
11+
}
12+
13+
public DependentResourceException(Throwable cause) {
14+
super(cause);
15+
}
16+
17+
public DependentResourceException(String message, Throwable cause) {
18+
super(message, cause);
19+
}
20+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
package io.javaoperatorsdk.operator.processing.dependent;
2+
3+
import java.util.Optional;
4+
5+
import org.junit.jupiter.api.Test;
6+
7+
import io.fabric8.kubernetes.api.model.ConfigMap;
8+
import io.fabric8.kubernetes.api.model.ObjectMetaBuilder;
9+
import io.javaoperatorsdk.operator.api.reconciler.Context;
10+
import io.javaoperatorsdk.operator.processing.event.ResourceID;
11+
import io.javaoperatorsdk.operator.sample.simple.TestCustomResource;
12+
13+
import static org.junit.jupiter.api.Assertions.*;
14+
import static org.mockito.Mockito.mock;
15+
import static org.mockito.Mockito.when;
16+
17+
class AbstractDependentResourceTest {
18+
19+
20+
@Test
21+
void throwsExceptionIfDesiredIsNullOnCreate() {
22+
TestDependentResource testDependentResource = new TestDependentResource();
23+
testDependentResource.setSecondary(null);
24+
testDependentResource.setDesired(null);
25+
26+
assertThrows(DependentResourceException.class,
27+
() -> testDependentResource.reconcile(new TestCustomResource(), null));
28+
29+
}
30+
31+
@Test
32+
void throwsExceptionIfDesiredIsNullOnUpdate() {
33+
TestDependentResource testDependentResource = new TestDependentResource();
34+
testDependentResource.setSecondary(configMap());
35+
testDependentResource.setDesired(null);
36+
37+
assertThrows(DependentResourceException.class,
38+
() -> testDependentResource.reconcile(new TestCustomResource(), null));
39+
}
40+
41+
@Test
42+
void throwsExceptionIfCreateReturnsNull() {
43+
TestDependentResource testDependentResource = new TestDependentResource();
44+
testDependentResource.setSecondary(null);
45+
testDependentResource.setDesired(configMap());
46+
47+
assertThrows(DependentResourceException.class,
48+
() -> testDependentResource.reconcile(new TestCustomResource(), null));
49+
}
50+
51+
@Test
52+
void throwsExceptionIfUpdateReturnsNull() {
53+
TestDependentResource testDependentResource = new TestDependentResource();
54+
testDependentResource.setSecondary(configMap());
55+
testDependentResource.setDesired(configMap());
56+
57+
assertThrows(DependentResourceException.class,
58+
() -> testDependentResource.reconcile(new TestCustomResource(), null));
59+
}
60+
61+
private ConfigMap configMap() {
62+
ConfigMap configMap = new ConfigMap();
63+
configMap.setMetadata(new ObjectMetaBuilder()
64+
.withName("test")
65+
.withNamespace("default")
66+
.build());
67+
return configMap;
68+
}
69+
70+
private static class TestDependentResource
71+
extends AbstractDependentResource<ConfigMap, TestCustomResource>
72+
implements Creator<ConfigMap, TestCustomResource>, Updater<ConfigMap, TestCustomResource> {
73+
74+
private ConfigMap secondary;
75+
private ConfigMap desired;
76+
77+
@Override
78+
public Class<ConfigMap> resourceType() {
79+
return ConfigMap.class;
80+
}
81+
82+
@Override
83+
public Optional<ConfigMap> getSecondaryResource(TestCustomResource primary) {
84+
return Optional.ofNullable(secondary);
85+
}
86+
87+
@Override
88+
protected void onCreated(ResourceID primaryResourceId, ConfigMap created) {}
89+
90+
@Override
91+
protected void onUpdated(ResourceID primaryResourceId, ConfigMap updated, ConfigMap actual) {}
92+
93+
@Override
94+
protected ConfigMap desired(TestCustomResource primary, Context<TestCustomResource> context) {
95+
return desired;
96+
}
97+
98+
public ConfigMap getSecondary() {
99+
return secondary;
100+
}
101+
102+
public TestDependentResource setSecondary(ConfigMap secondary) {
103+
this.secondary = secondary;
104+
return this;
105+
}
106+
107+
public ConfigMap getDesired() {
108+
return desired;
109+
}
110+
111+
public TestDependentResource setDesired(ConfigMap desired) {
112+
this.desired = desired;
113+
return this;
114+
}
115+
116+
@Override
117+
public ConfigMap create(ConfigMap desired, TestCustomResource primary,
118+
Context<TestCustomResource> context) {
119+
return null;
120+
}
121+
122+
@Override
123+
public ConfigMap update(ConfigMap actual, ConfigMap desired, TestCustomResource primary,
124+
Context<TestCustomResource> context) {
125+
return null;
126+
}
127+
128+
@Override
129+
public Matcher.Result<ConfigMap> match(ConfigMap actualResource, TestCustomResource primary,
130+
Context<TestCustomResource> context) {
131+
var result = mock(Matcher.Result.class);
132+
when(result.matched()).thenReturn(false);
133+
return result;
134+
}
135+
}
136+
}

0 commit comments

Comments
 (0)