Skip to content

Commit 8c341ae

Browse files
csvirimetacosm
authored andcommitted
fix: issue with cluster scoped resource (#1549)
1 parent bd03a1c commit 8c341ae

File tree

7 files changed

+217
-18
lines changed

7 files changed

+217
-18
lines changed

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

+24-17
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,11 @@
99

1010
import io.fabric8.kubernetes.api.model.HasMetadata;
1111
import io.fabric8.kubernetes.api.model.KubernetesResourceList;
12+
import io.fabric8.kubernetes.api.model.Namespaced;
1213
import io.fabric8.kubernetes.client.CustomResource;
1314
import io.fabric8.kubernetes.client.KubernetesClientException;
1415
import io.fabric8.kubernetes.client.dsl.MixedOperation;
16+
import io.fabric8.kubernetes.client.dsl.NonNamespaceOperation;
1517
import io.fabric8.kubernetes.client.dsl.Resource;
1618
import io.fabric8.kubernetes.client.dsl.internal.HasMetadataOperationsImpl;
1719
import io.fabric8.kubernetes.client.utils.Serialization;
@@ -170,7 +172,7 @@ private PostExecutionControl<P> handleErrorStatusHandler(P resource, P originalR
170172
Exception e) throws Exception {
171173
if (isErrorStatusHandlerPresent()) {
172174
try {
173-
RetryInfo retryInfo = context.getRetryInfo().orElse(new RetryInfo() {
175+
RetryInfo retryInfo = context.getRetryInfo().orElseGet(() -> new RetryInfo() {
174176
@Override
175177
public int getAttemptCount() {
176178
return 0;
@@ -362,28 +364,28 @@ public CustomResourceFacade(
362364
}
363365

364366
public R getResource(String namespace, String name) {
365-
return resourceOperation.inNamespace(namespace).withName(name).get();
367+
if (namespace != null) {
368+
return resourceOperation.inNamespace(namespace).withName(name).get();
369+
} else {
370+
return resourceOperation.withName(name).get();
371+
}
366372
}
367373

368374
public R updateResource(R resource) {
369-
log.debug(
370-
"Trying to replace resource {}, version: {}",
371-
getName(resource),
372-
resource.getMetadata().getResourceVersion());
373-
return resourceOperation
374-
.inNamespace(resource.getMetadata().getNamespace())
375-
.withName(getName(resource))
376-
.lockResourceVersion(resource.getMetadata().getResourceVersion())
377-
.replace(resource);
375+
final var resourceVersion = resource.getMetadata().getResourceVersion();
376+
log.debug("Trying to replace resource {}, version: {}", getName(resource), resourceVersion);
377+
378+
return resource(resource).withName(resource.getMetadata().getName())
379+
.lockResourceVersion(resourceVersion).replace(resource);
378380
}
379381

380382
@SuppressWarnings({"rawtypes", "unchecked"})
381383
public R updateStatus(R resource) {
382384
log.trace("Updating status for resource: {}", resource);
383-
HasMetadataOperationsImpl hasMetadataOperation = (HasMetadataOperationsImpl) resourceOperation
384-
.inNamespace(resource.getMetadata().getNamespace())
385-
.withName(getName(resource))
386-
.lockResourceVersion(resource.getMetadata().getResourceVersion());
385+
HasMetadataOperationsImpl hasMetadataOperation =
386+
(HasMetadataOperationsImpl) resource(resource)
387+
.withName(getName(resource))
388+
.lockResourceVersion(resource.getMetadata().getResourceVersion());
387389
return (R) hasMetadataOperation.replaceStatus(resource);
388390
}
389391

@@ -395,8 +397,7 @@ public R patchStatus(R resource, R originalResource) {
395397
resource.getMetadata().setResourceVersion(null);
396398
try (var bis = new ByteArrayInputStream(
397399
Serialization.asJson(originalResource).getBytes(StandardCharsets.UTF_8))) {
398-
return resourceOperation
399-
.inNamespace(resource.getMetadata().getNamespace())
400+
return resource(originalResource)
400401
// will be simplified in fabric8 v6
401402
.load(bis)
402403
.editStatus(r -> resource);
@@ -408,5 +409,11 @@ public R patchStatus(R resource, R originalResource) {
408409
resource.getMetadata().setResourceVersion(resourceVersion);
409410
}
410411
}
412+
413+
private NonNamespaceOperation<R, KubernetesResourceList<R>, Resource<R>> resource(R resource) {
414+
return resource instanceof Namespaced
415+
? resourceOperation.inNamespace(resource.getMetadata().getNamespace())
416+
: resourceOperation;
417+
}
411418
}
412419
}

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.LocalPortForward;
2021
import io.javaoperatorsdk.operator.Operator;
@@ -124,7 +125,11 @@ protected void before(ExtensionContext context) {
124125
final var configurationService = ConfigurationServiceProvider.instance();
125126
for (var ref : reconcilers) {
126127
final var config = configurationService.getConfigurationFor(ref.reconciler);
127-
final var oconfig = override(config).settingNamespace(namespace);
128+
final var oconfig = override(config);
129+
130+
if (Namespaced.class.isAssignableFrom(config.getResourceClass())) {
131+
oconfig.settingNamespace(namespace);
132+
}
128133

129134
if (ref.retry != null) {
130135
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,66 @@
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.fabric8.kubernetes.client.dsl.Resource;
10+
import io.javaoperatorsdk.operator.api.reconciler.*;
11+
import io.javaoperatorsdk.operator.junit.KubernetesClientAware;
12+
13+
@ControllerConfiguration
14+
public class ClusterScopedCustomResourceReconciler
15+
implements Reconciler<ClusterScopedCustomResource>, Cleaner<ClusterScopedCustomResource>,
16+
KubernetesClientAware {
17+
18+
public static final String DATA_KEY = "data-key";
19+
20+
private KubernetesClient client;
21+
22+
@Override
23+
public UpdateControl<ClusterScopedCustomResource> reconcile(
24+
ClusterScopedCustomResource resource, Context<ClusterScopedCustomResource> context) {
25+
26+
final var desired = desired(resource);
27+
getConfigMapResource(desired).createOrReplace(desired);
28+
29+
resource.setStatus(new ClusterScopedCustomResourceStatus());
30+
resource.getStatus().setCreated(true);
31+
return UpdateControl.patchStatus(resource);
32+
}
33+
34+
private Resource<ConfigMap> getConfigMapResource(ConfigMap desired) {
35+
return client.configMaps().inNamespace(desired.getMetadata().getNamespace())
36+
.withName(desired.getMetadata().getName());
37+
}
38+
39+
private ConfigMap desired(ClusterScopedCustomResource resource) {
40+
return new ConfigMapBuilder()
41+
.withMetadata(new ObjectMetaBuilder()
42+
.withName(resource.getMetadata().getName())
43+
.withNamespace(resource.getSpec().getTargetNamespace())
44+
.build())
45+
.withData(Map.of(DATA_KEY, resource.getSpec().getData()))
46+
.build();
47+
}
48+
49+
@Override
50+
public KubernetesClient getKubernetesClient() {
51+
return client;
52+
}
53+
54+
@Override
55+
public void setKubernetesClient(KubernetesClient kubernetesClient) {
56+
this.client = kubernetesClient;
57+
}
58+
59+
@Override
60+
public DeleteControl cleanup(ClusterScopedCustomResource resource,
61+
Context<ClusterScopedCustomResource> context) {
62+
final var desired = desired(resource);
63+
getConfigMapResource(desired).delete();
64+
return DeleteControl.defaultDelete();
65+
}
66+
}
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)