Skip to content

Commit 86515f6

Browse files
authored
fix: issue with cluster scoped resource (#1549)
1 parent b97d36e commit 86515f6

File tree

7 files changed

+203
-15
lines changed

7 files changed

+203
-15
lines changed

operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/ReconciliationDispatcher.java

+18-14
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,11 @@
55

66
import io.fabric8.kubernetes.api.model.HasMetadata;
77
import io.fabric8.kubernetes.api.model.KubernetesResourceList;
8+
import io.fabric8.kubernetes.api.model.Namespaced;
89
import io.fabric8.kubernetes.client.CustomResource;
910
import io.fabric8.kubernetes.client.KubernetesClientException;
1011
import io.fabric8.kubernetes.client.dsl.MixedOperation;
1112
import io.fabric8.kubernetes.client.dsl.Resource;
12-
import io.fabric8.kubernetes.client.dsl.internal.HasMetadataOperationsImpl;
1313
import io.javaoperatorsdk.operator.OperatorException;
1414
import io.javaoperatorsdk.operator.api.ObservedGenerationAware;
1515
import io.javaoperatorsdk.operator.api.config.ConfigurationServiceProvider;
@@ -355,29 +355,29 @@ public CustomResourceFacade(
355355
}
356356

357357
public R getResource(String namespace, String name) {
358-
return resourceOperation.inNamespace(namespace).withName(name).get();
358+
if (namespace != null) {
359+
return resourceOperation.inNamespace(namespace).withName(name).get();
360+
} else {
361+
return resourceOperation.withName(name).get();
362+
}
359363
}
360364

361365
public R updateResource(R resource) {
362366
log.debug(
363367
"Trying to replace resource {}, version: {}",
364368
getName(resource),
365369
resource.getMetadata().getResourceVersion());
366-
return resourceOperation
367-
.inNamespace(resource.getMetadata().getNamespace())
368-
.resource(resource)
369-
.lockResourceVersion(resource.getMetadata().getResourceVersion())
370+
371+
return resource(resource).lockResourceVersion(resource.getMetadata().getResourceVersion())
370372
.replace();
371373
}
372374

373375
@SuppressWarnings({"rawtypes", "unchecked"})
374376
public R updateStatus(R resource) {
375377
log.trace("Updating status for resource: {}", resource);
376-
HasMetadataOperationsImpl hasMetadataOperation = (HasMetadataOperationsImpl) resourceOperation
377-
.inNamespace(resource.getMetadata().getNamespace())
378-
.withName(getName(resource))
379-
.lockResourceVersion(resource.getMetadata().getResourceVersion());
380-
return (R) hasMetadataOperation.replaceStatus(resource);
378+
return resource(resource)
379+
.lockResourceVersion()
380+
.replaceStatus();
381381
}
382382

383383
public R patchStatus(R resource, R originalResource) {
@@ -387,15 +387,19 @@ public R patchStatus(R resource, R originalResource) {
387387
originalResource.getMetadata().setResourceVersion(null);
388388
resource.getMetadata().setResourceVersion(null);
389389
try {
390-
return resourceOperation
391-
.inNamespace(resource.getMetadata().getNamespace())
392-
.resource(originalResource)
390+
return resource(originalResource)
393391
.editStatus(r -> resource);
394392
} finally {
395393
// restore initial resource version
396394
originalResource.getMetadata().setResourceVersion(resourceVersion);
397395
resource.getMetadata().setResourceVersion(resourceVersion);
398396
}
399397
}
398+
399+
private Resource<R> resource(R resource) {
400+
return resource instanceof Namespaced ? resourceOperation
401+
.inNamespace(resource.getMetadata().getNamespace())
402+
.resource(resource) : resourceOperation.resource(resource);
403+
}
400404
}
401405
}

operator-framework-junit5/src/main/java/io/javaoperatorsdk/operator/junit/LocallyRunOperatorExtension.java

+6-1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import org.slf4j.LoggerFactory;
1616

1717
import io.fabric8.kubernetes.api.model.HasMetadata;
18+
import io.fabric8.kubernetes.api.model.Namespaced;
1819
import io.fabric8.kubernetes.client.CustomResource;
1920
import io.fabric8.kubernetes.client.KubernetesClient;
2021
import io.fabric8.kubernetes.client.LocalPortForward;
@@ -128,7 +129,11 @@ protected void before(ExtensionContext context) {
128129

129130
for (var ref : reconcilers) {
130131
final var config = configurationService.getConfigurationFor(ref.reconciler);
131-
final var oconfig = override(config).settingNamespace(namespace);
132+
final var oconfig = override(config);
133+
134+
if (Namespaced.class.isAssignableFrom(config.getResourceClass())) {
135+
oconfig.settingNamespace(namespace);
136+
}
132137

133138
if (ref.retry != null) {
134139
oconfig.withRetry(ref.retry);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
package io.javaoperatorsdk.operator;
2+
3+
import org.junit.jupiter.api.Test;
4+
import org.junit.jupiter.api.extension.RegisterExtension;
5+
6+
import io.fabric8.kubernetes.api.model.ConfigMap;
7+
import io.fabric8.kubernetes.api.model.ObjectMetaBuilder;
8+
import io.javaoperatorsdk.operator.junit.LocallyRunOperatorExtension;
9+
import io.javaoperatorsdk.operator.sample.clusterscopedresource.ClusterScopedCustomResource;
10+
import io.javaoperatorsdk.operator.sample.clusterscopedresource.ClusterScopedCustomResourceReconciler;
11+
import io.javaoperatorsdk.operator.sample.clusterscopedresource.ClusterScopedCustomResourceSpec;
12+
13+
import static org.assertj.core.api.Assertions.assertThat;
14+
import static org.awaitility.Awaitility.await;
15+
16+
class ClusterScopedResourceIT {
17+
18+
public static final String TEST_NAME = "test1";
19+
public static final String INITIAL_DATA = "initialData";
20+
public static final String UPDATED_DATA = "updatedData";
21+
@RegisterExtension
22+
LocallyRunOperatorExtension operator =
23+
LocallyRunOperatorExtension.builder()
24+
.withReconciler(new ClusterScopedCustomResourceReconciler()).build();
25+
26+
@Test
27+
void crudOperationOnClusterScopedCustomResource() {
28+
var resource = operator.create(testResource());
29+
30+
await().untilAsserted(() -> {
31+
var res = operator.get(ClusterScopedCustomResource.class, TEST_NAME);
32+
assertThat(res.getStatus()).isNotNull();
33+
assertThat(res.getStatus().getCreated()).isTrue();
34+
var cm = operator.get(ConfigMap.class, TEST_NAME);
35+
assertThat(cm).isNotNull();
36+
assertThat(cm.getData().get(ClusterScopedCustomResourceReconciler.DATA_KEY))
37+
.isEqualTo(INITIAL_DATA);
38+
});
39+
40+
resource.getSpec().setData(UPDATED_DATA);
41+
operator.replace(resource);
42+
await().untilAsserted(() -> {
43+
var cm = operator.get(ConfigMap.class, TEST_NAME);
44+
assertThat(cm).isNotNull();
45+
assertThat(cm.getData().get(ClusterScopedCustomResourceReconciler.DATA_KEY))
46+
.isEqualTo(UPDATED_DATA);
47+
});
48+
49+
operator.delete(resource);
50+
await().untilAsserted(() -> assertThat(operator.get(ConfigMap.class, TEST_NAME)).isNull());
51+
}
52+
53+
54+
ClusterScopedCustomResource testResource() {
55+
var res = new ClusterScopedCustomResource();
56+
res.setMetadata(new ObjectMetaBuilder()
57+
.withName(TEST_NAME)
58+
.build());
59+
res.setSpec(new ClusterScopedCustomResourceSpec());
60+
res.getSpec().setTargetNamespace(operator.getNamespace());
61+
res.getSpec().setData(INITIAL_DATA);
62+
63+
return res;
64+
}
65+
66+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package io.javaoperatorsdk.operator.sample.clusterscopedresource;
2+
3+
import io.fabric8.kubernetes.client.CustomResource;
4+
import io.fabric8.kubernetes.model.annotation.Group;
5+
import io.fabric8.kubernetes.model.annotation.ShortNames;
6+
import io.fabric8.kubernetes.model.annotation.Version;
7+
8+
@Group("sample.javaoperatorsdk")
9+
@Version("v1")
10+
@ShortNames("csc")
11+
public class ClusterScopedCustomResource
12+
extends CustomResource<ClusterScopedCustomResourceSpec, ClusterScopedCustomResourceStatus> {
13+
14+
15+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package io.javaoperatorsdk.operator.sample.clusterscopedresource;
2+
3+
import java.util.Map;
4+
5+
import io.fabric8.kubernetes.api.model.ConfigMap;
6+
import io.fabric8.kubernetes.api.model.ConfigMapBuilder;
7+
import io.fabric8.kubernetes.api.model.ObjectMetaBuilder;
8+
import io.fabric8.kubernetes.client.KubernetesClient;
9+
import io.javaoperatorsdk.operator.api.reconciler.*;
10+
import io.javaoperatorsdk.operator.junit.KubernetesClientAware;
11+
12+
@ControllerConfiguration
13+
public class ClusterScopedCustomResourceReconciler
14+
implements Reconciler<ClusterScopedCustomResource>, Cleaner<ClusterScopedCustomResource>,
15+
KubernetesClientAware {
16+
17+
public static final String DATA_KEY = "data-key";
18+
19+
private KubernetesClient client;
20+
21+
@Override
22+
public UpdateControl<ClusterScopedCustomResource> reconcile(
23+
ClusterScopedCustomResource resource, Context<ClusterScopedCustomResource> context) {
24+
25+
client.configMaps().resource(desired(resource)).createOrReplace();
26+
27+
resource.setStatus(new ClusterScopedCustomResourceStatus());
28+
resource.getStatus().setCreated(true);
29+
return UpdateControl.patchStatus(resource);
30+
}
31+
32+
private ConfigMap desired(ClusterScopedCustomResource resource) {
33+
return new ConfigMapBuilder()
34+
.withMetadata(new ObjectMetaBuilder()
35+
.withName(resource.getMetadata().getName())
36+
.withNamespace(resource.getSpec().getTargetNamespace())
37+
.build())
38+
.withData(Map.of(DATA_KEY, resource.getSpec().getData()))
39+
.build();
40+
}
41+
42+
@Override
43+
public KubernetesClient getKubernetesClient() {
44+
return client;
45+
}
46+
47+
@Override
48+
public void setKubernetesClient(KubernetesClient kubernetesClient) {
49+
this.client = kubernetesClient;
50+
}
51+
52+
@Override
53+
public DeleteControl cleanup(ClusterScopedCustomResource resource,
54+
Context<ClusterScopedCustomResource> context) {
55+
client.configMaps().resource(desired(resource)).delete();
56+
return DeleteControl.defaultDelete();
57+
}
58+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package io.javaoperatorsdk.operator.sample.clusterscopedresource;
2+
3+
public class ClusterScopedCustomResourceSpec {
4+
5+
private String data;
6+
private String targetNamespace;
7+
8+
public String getData() {
9+
return data;
10+
}
11+
12+
public ClusterScopedCustomResourceSpec setData(String data) {
13+
this.data = data;
14+
return this;
15+
}
16+
17+
public String getTargetNamespace() {
18+
return targetNamespace;
19+
}
20+
21+
public ClusterScopedCustomResourceSpec setTargetNamespace(String targetNamespace) {
22+
this.targetNamespace = targetNamespace;
23+
return this;
24+
}
25+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package io.javaoperatorsdk.operator.sample.clusterscopedresource;
2+
3+
public class ClusterScopedCustomResourceStatus {
4+
5+
private Boolean created;
6+
7+
public Boolean getCreated() {
8+
return created;
9+
}
10+
11+
public ClusterScopedCustomResourceStatus setCreated(Boolean created) {
12+
this.created = created;
13+
return this;
14+
}
15+
}

0 commit comments

Comments
 (0)