Skip to content

Commit

Permalink
fix: issue with cluster scoped resource (#1549) (#1558)
Browse files Browse the repository at this point in the history
Co-authored-by: Attila Mészáros <[email protected]>
  • Loading branch information
metacosm and csviri authored Oct 21, 2022
1 parent bd03a1c commit dfae8d5
Show file tree
Hide file tree
Showing 7 changed files with 217 additions and 18 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,11 @@

import io.fabric8.kubernetes.api.model.HasMetadata;
import io.fabric8.kubernetes.api.model.KubernetesResourceList;
import io.fabric8.kubernetes.api.model.Namespaced;
import io.fabric8.kubernetes.client.CustomResource;
import io.fabric8.kubernetes.client.KubernetesClientException;
import io.fabric8.kubernetes.client.dsl.MixedOperation;
import io.fabric8.kubernetes.client.dsl.NonNamespaceOperation;
import io.fabric8.kubernetes.client.dsl.Resource;
import io.fabric8.kubernetes.client.dsl.internal.HasMetadataOperationsImpl;
import io.fabric8.kubernetes.client.utils.Serialization;
Expand Down Expand Up @@ -170,7 +172,7 @@ private PostExecutionControl<P> handleErrorStatusHandler(P resource, P originalR
Exception e) throws Exception {
if (isErrorStatusHandlerPresent()) {
try {
RetryInfo retryInfo = context.getRetryInfo().orElse(new RetryInfo() {
RetryInfo retryInfo = context.getRetryInfo().orElseGet(() -> new RetryInfo() {
@Override
public int getAttemptCount() {
return 0;
Expand Down Expand Up @@ -362,28 +364,28 @@ public CustomResourceFacade(
}

public R getResource(String namespace, String name) {
return resourceOperation.inNamespace(namespace).withName(name).get();
if (namespace != null) {
return resourceOperation.inNamespace(namespace).withName(name).get();
} else {
return resourceOperation.withName(name).get();
}
}

public R updateResource(R resource) {
log.debug(
"Trying to replace resource {}, version: {}",
getName(resource),
resource.getMetadata().getResourceVersion());
return resourceOperation
.inNamespace(resource.getMetadata().getNamespace())
.withName(getName(resource))
.lockResourceVersion(resource.getMetadata().getResourceVersion())
.replace(resource);
final var resourceVersion = resource.getMetadata().getResourceVersion();
log.debug("Trying to replace resource {}, version: {}", getName(resource), resourceVersion);

return resource(resource).withName(resource.getMetadata().getName())
.lockResourceVersion(resourceVersion).replace(resource);
}

@SuppressWarnings({"rawtypes", "unchecked"})
public R updateStatus(R resource) {
log.trace("Updating status for resource: {}", resource);
HasMetadataOperationsImpl hasMetadataOperation = (HasMetadataOperationsImpl) resourceOperation
.inNamespace(resource.getMetadata().getNamespace())
.withName(getName(resource))
.lockResourceVersion(resource.getMetadata().getResourceVersion());
HasMetadataOperationsImpl hasMetadataOperation =
(HasMetadataOperationsImpl) resource(resource)
.withName(getName(resource))
.lockResourceVersion(resource.getMetadata().getResourceVersion());
return (R) hasMetadataOperation.replaceStatus(resource);
}

Expand All @@ -395,8 +397,7 @@ public R patchStatus(R resource, R originalResource) {
resource.getMetadata().setResourceVersion(null);
try (var bis = new ByteArrayInputStream(
Serialization.asJson(originalResource).getBytes(StandardCharsets.UTF_8))) {
return resourceOperation
.inNamespace(resource.getMetadata().getNamespace())
return resource(originalResource)
// will be simplified in fabric8 v6
.load(bis)
.editStatus(r -> resource);
Expand All @@ -408,5 +409,11 @@ public R patchStatus(R resource, R originalResource) {
resource.getMetadata().setResourceVersion(resourceVersion);
}
}

private NonNamespaceOperation<R, KubernetesResourceList<R>, Resource<R>> resource(R resource) {
return resource instanceof Namespaced
? resourceOperation.inNamespace(resource.getMetadata().getNamespace())
: resourceOperation;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import org.slf4j.LoggerFactory;

import io.fabric8.kubernetes.api.model.HasMetadata;
import io.fabric8.kubernetes.api.model.Namespaced;
import io.fabric8.kubernetes.client.CustomResource;
import io.fabric8.kubernetes.client.LocalPortForward;
import io.javaoperatorsdk.operator.Operator;
Expand Down Expand Up @@ -124,7 +125,11 @@ protected void before(ExtensionContext context) {
final var configurationService = ConfigurationServiceProvider.instance();
for (var ref : reconcilers) {
final var config = configurationService.getConfigurationFor(ref.reconciler);
final var oconfig = override(config).settingNamespace(namespace);
final var oconfig = override(config);

if (Namespaced.class.isAssignableFrom(config.getResourceClass())) {
oconfig.settingNamespace(namespace);
}

if (ref.retry != null) {
oconfig.withRetry(ref.retry);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package io.javaoperatorsdk.operator;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;

import io.fabric8.kubernetes.api.model.ConfigMap;
import io.fabric8.kubernetes.api.model.ObjectMetaBuilder;
import io.javaoperatorsdk.operator.junit.LocallyRunOperatorExtension;
import io.javaoperatorsdk.operator.sample.clusterscopedresource.ClusterScopedCustomResource;
import io.javaoperatorsdk.operator.sample.clusterscopedresource.ClusterScopedCustomResourceReconciler;
import io.javaoperatorsdk.operator.sample.clusterscopedresource.ClusterScopedCustomResourceSpec;

import static org.assertj.core.api.Assertions.assertThat;
import static org.awaitility.Awaitility.await;

class ClusterScopedResourceIT {

public static final String TEST_NAME = "test1";
public static final String INITIAL_DATA = "initialData";
public static final String UPDATED_DATA = "updatedData";
@RegisterExtension
LocallyRunOperatorExtension operator =
LocallyRunOperatorExtension.builder()
.withReconciler(new ClusterScopedCustomResourceReconciler()).build();

@Test
void crudOperationOnClusterScopedCustomResource() {
var resource = operator.create(testResource());

await().untilAsserted(() -> {
var res = operator.get(ClusterScopedCustomResource.class, TEST_NAME);
assertThat(res.getStatus()).isNotNull();
assertThat(res.getStatus().getCreated()).isTrue();
var cm = operator.get(ConfigMap.class, TEST_NAME);
assertThat(cm).isNotNull();
assertThat(cm.getData().get(ClusterScopedCustomResourceReconciler.DATA_KEY))
.isEqualTo(INITIAL_DATA);
});

resource.getSpec().setData(UPDATED_DATA);
operator.replace(resource);
await().untilAsserted(() -> {
var cm = operator.get(ConfigMap.class, TEST_NAME);
assertThat(cm).isNotNull();
assertThat(cm.getData().get(ClusterScopedCustomResourceReconciler.DATA_KEY))
.isEqualTo(UPDATED_DATA);
});

operator.delete(resource);
await().untilAsserted(() -> assertThat(operator.get(ConfigMap.class, TEST_NAME)).isNull());
}


ClusterScopedCustomResource testResource() {
var res = new ClusterScopedCustomResource();
res.setMetadata(new ObjectMetaBuilder()
.withName(TEST_NAME)
.build());
res.setSpec(new ClusterScopedCustomResourceSpec());
res.getSpec().setTargetNamespace(operator.getNamespace());
res.getSpec().setData(INITIAL_DATA);

return res;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package io.javaoperatorsdk.operator.sample.clusterscopedresource;

import io.fabric8.kubernetes.client.CustomResource;
import io.fabric8.kubernetes.model.annotation.Group;
import io.fabric8.kubernetes.model.annotation.ShortNames;
import io.fabric8.kubernetes.model.annotation.Version;

@Group("sample.javaoperatorsdk")
@Version("v1")
@ShortNames("csc")
public class ClusterScopedCustomResource
extends CustomResource<ClusterScopedCustomResourceSpec, ClusterScopedCustomResourceStatus> {


}
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package io.javaoperatorsdk.operator.sample.clusterscopedresource;

import java.util.Map;

import io.fabric8.kubernetes.api.model.ConfigMap;
import io.fabric8.kubernetes.api.model.ConfigMapBuilder;
import io.fabric8.kubernetes.api.model.ObjectMetaBuilder;
import io.fabric8.kubernetes.client.KubernetesClient;
import io.fabric8.kubernetes.client.dsl.Resource;
import io.javaoperatorsdk.operator.api.reconciler.*;
import io.javaoperatorsdk.operator.junit.KubernetesClientAware;

@ControllerConfiguration
public class ClusterScopedCustomResourceReconciler
implements Reconciler<ClusterScopedCustomResource>, Cleaner<ClusterScopedCustomResource>,
KubernetesClientAware {

public static final String DATA_KEY = "data-key";

private KubernetesClient client;

@Override
public UpdateControl<ClusterScopedCustomResource> reconcile(
ClusterScopedCustomResource resource, Context<ClusterScopedCustomResource> context) {

final var desired = desired(resource);
getConfigMapResource(desired).createOrReplace(desired);

resource.setStatus(new ClusterScopedCustomResourceStatus());
resource.getStatus().setCreated(true);
return UpdateControl.patchStatus(resource);
}

private Resource<ConfigMap> getConfigMapResource(ConfigMap desired) {
return client.configMaps().inNamespace(desired.getMetadata().getNamespace())
.withName(desired.getMetadata().getName());
}

private ConfigMap desired(ClusterScopedCustomResource resource) {
return new ConfigMapBuilder()
.withMetadata(new ObjectMetaBuilder()
.withName(resource.getMetadata().getName())
.withNamespace(resource.getSpec().getTargetNamespace())
.build())
.withData(Map.of(DATA_KEY, resource.getSpec().getData()))
.build();
}

@Override
public KubernetesClient getKubernetesClient() {
return client;
}

@Override
public void setKubernetesClient(KubernetesClient kubernetesClient) {
this.client = kubernetesClient;
}

@Override
public DeleteControl cleanup(ClusterScopedCustomResource resource,
Context<ClusterScopedCustomResource> context) {
final var desired = desired(resource);
getConfigMapResource(desired).delete();
return DeleteControl.defaultDelete();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package io.javaoperatorsdk.operator.sample.clusterscopedresource;

public class ClusterScopedCustomResourceSpec {

private String data;
private String targetNamespace;

public String getData() {
return data;
}

public ClusterScopedCustomResourceSpec setData(String data) {
this.data = data;
return this;
}

public String getTargetNamespace() {
return targetNamespace;
}

public ClusterScopedCustomResourceSpec setTargetNamespace(String targetNamespace) {
this.targetNamespace = targetNamespace;
return this;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package io.javaoperatorsdk.operator.sample.clusterscopedresource;

public class ClusterScopedCustomResourceStatus {

private Boolean created;

public Boolean getCreated() {
return created;
}

public ClusterScopedCustomResourceStatus setCreated(Boolean created) {
this.created = created;
return this;
}
}

0 comments on commit dfae8d5

Please sign in to comment.