diff --git a/operator-framework-core/pom.xml b/operator-framework-core/pom.xml
index a615428790..e5b42dfbb9 100644
--- a/operator-framework-core/pom.xml
+++ b/operator-framework-core/pom.xml
@@ -56,6 +56,7 @@
io.fabric8
openshift-client
+ ${fabric8-client.version}
@@ -98,6 +99,7 @@
io.fabric8
kubernetes-server-mock
+ ${fabric8-client.version}
test
diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/ReconcilerUtils.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/ReconcilerUtils.java
index 26dc4cdcca..95aa7066c0 100644
--- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/ReconcilerUtils.java
+++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/ReconcilerUtils.java
@@ -45,7 +45,8 @@ public static String getResourceTypeName(Class extends HasMetadata> resourceCl
// return HasMetadata.getFullResourceName(resourceClass);
final var group = HasMetadata.getGroup(resourceClass);
final var plural = HasMetadata.getPlural(resourceClass);
- return (group == null || group.isEmpty()) ? plural : plural + "." + group;
+ final var version = HasMetadata.getVersion(resourceClass);
+ return (group == null || group.isEmpty()) ? plural : plural + "." + group + "/" + version;
}
public static String getDefaultFinalizerName(Class extends HasMetadata> resourceClass) {
@@ -83,15 +84,16 @@ public static String getDefaultNameFor(Reconciler reconciler) {
}
public static String getDefaultNameFor(Class extends Reconciler> reconcilerClass) {
- return getDefaultReconcilerName(reconcilerClass.getSimpleName());
+ return getDefaultReconcilerName(reconcilerClass.getCanonicalName());
}
public static String getDefaultReconcilerName(String reconcilerClassName) {
- // if the name is fully qualified, extract the simple class name
- final var lastDot = reconcilerClassName.lastIndexOf('.');
- if (lastDot > 0) {
- reconcilerClassName = reconcilerClassName.substring(lastDot + 1);
- }
+ // TODO: check why this logic was imlpemented in first place
+ // // if the name is fully qualified, extract the simple class name
+ // final var lastDot = reconcilerClassName.lastIndexOf('.');
+ // if (lastDot > 0) {
+ // reconcilerClassName = reconcilerClassName.substring(lastDot + 1);
+ // }
return reconcilerClassName.toLowerCase(Locale.ROOT);
}
}
diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/Controller.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/Controller.java
index 5e93b0d7af..065d6c8fd8 100644
--- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/Controller.java
+++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/Controller.java
@@ -3,6 +3,8 @@
import java.util.List;
import java.util.Objects;
+import io.fabric8.kubernetes.api.model.GenericKubernetesResource;
+import io.fabric8.kubernetes.api.model.GenericKubernetesResourceList;
import io.fabric8.kubernetes.api.model.HasMetadata;
import io.fabric8.kubernetes.api.model.KubernetesResourceList;
import io.fabric8.kubernetes.api.model.apiextensions.v1.CustomResourceDefinition;
@@ -10,6 +12,8 @@
import io.fabric8.kubernetes.client.KubernetesClient;
import io.fabric8.kubernetes.client.dsl.MixedOperation;
import io.fabric8.kubernetes.client.dsl.Resource;
+import io.fabric8.kubernetes.client.dsl.base.ResourceDefinitionContext;
+import io.fabric8.kubernetes.internal.KubernetesDeserializer;
import io.javaoperatorsdk.operator.CustomResourceUtils;
import io.javaoperatorsdk.operator.MissingCRDException;
import io.javaoperatorsdk.operator.OperatorException;
@@ -152,6 +156,13 @@ public MixedOperation, Resource> getCRClient() {
return kubernetesClient.resources(configuration.getResourceClass());
}
+ public MixedOperation> getGenericClient() {
+ var context = ResourceDefinitionContext.fromResourceType(configuration.getResourceClass());
+ KubernetesDeserializer.registerCustomKind(context.getVersion(), context.getKind(),
+ GenericKubernetesResource.class);
+ return kubernetesClient.genericKubernetesResources(context);
+ }
+
/**
* Registers the specified controller with this operator, overriding its default configuration by
* the specified one (usually created via
diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/ReconciliationDispatcher.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/ReconciliationDispatcher.java
index 52374c5645..07cc6d1850 100644
--- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/ReconciliationDispatcher.java
+++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/ReconciliationDispatcher.java
@@ -43,7 +43,7 @@ class ReconciliationDispatcher {
}
public ReconciliationDispatcher(Controller controller) {
- this(controller, new CustomResourceFacade<>(controller.getCRClient()));
+ this(controller, new CustomResourceFacade(controller.getCRClient()));
}
public PostExecutionControl handleExecution(ExecutionScope executionScope) {
diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/controller/ControllerResourceCache.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/controller/ControllerResourceCache.java
index 25c08d6af6..537a94ce90 100644
--- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/controller/ControllerResourceCache.java
+++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/controller/ControllerResourceCache.java
@@ -2,11 +2,14 @@
import java.util.Map;
import java.util.Optional;
+import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Stream;
+import io.fabric8.kubernetes.api.model.GenericKubernetesResource;
import io.fabric8.kubernetes.api.model.HasMetadata;
import io.fabric8.kubernetes.client.informers.SharedIndexInformer;
+import io.fabric8.kubernetes.client.utils.Serialization;
import io.javaoperatorsdk.operator.api.config.Cloner;
import io.javaoperatorsdk.operator.processing.event.ResourceID;
import io.javaoperatorsdk.operator.processing.event.source.Cache;
@@ -17,30 +20,37 @@
public class ControllerResourceCache implements ResourceCache {
- private final Map> sharedIndexInformers;
+ private final Map> sharedIndexInformers;
private final Cloner cloner;
- public ControllerResourceCache(Map> sharedIndexInformers,
+ public ControllerResourceCache(
+ Map> sharedIndexInformers,
Cloner cloner) {
this.sharedIndexInformers = sharedIndexInformers;
this.cloner = cloner;
}
+ private Function CONVERT =
+ resource -> Serialization.unmarshal(Serialization.asJson(resource));
+
@Override
public Stream list(Predicate predicate) {
return sharedIndexInformers.values().stream()
- .flatMap(i -> i.getStore().list().stream().filter(predicate));
+ .flatMap(i -> i.getStore().list().stream()
+ .map(CONVERT).filter(predicate));
}
@Override
public Stream list(String namespace, Predicate predicate) {
if (isWatchingAllNamespaces()) {
final var stream = sharedIndexInformers.get(ANY_NAMESPACE_MAP_KEY).getStore().list().stream()
- .filter(r -> r.getMetadata().getNamespace().equals(namespace));
+ .map(CONVERT).filter(r -> r.getMetadata().getNamespace().equals(namespace));
return predicate != null ? stream.filter(predicate) : stream;
} else {
final var informer = sharedIndexInformers.get(namespace);
- return informer != null ? informer.getStore().list().stream().filter(predicate)
+ return informer != null
+ ? informer.getStore().list().stream()
+ .map(CONVERT).filter(predicate)
: Stream.empty();
}
}
@@ -59,7 +69,7 @@ public Optional get(ResourceID resourceID) {
if (resource == null) {
return Optional.empty();
} else {
- return Optional.of(cloner.clone(resource));
+ return Optional.of(cloner.clone(resource)).map(CONVERT);
}
}
diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/controller/ControllerResourceEventSource.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/controller/ControllerResourceEventSource.java
index 9feabf40bf..1138c53f5d 100644
--- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/controller/ControllerResourceEventSource.java
+++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/controller/ControllerResourceEventSource.java
@@ -9,12 +9,14 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import io.fabric8.kubernetes.api.model.GenericKubernetesResource;
+import io.fabric8.kubernetes.api.model.GenericKubernetesResourceList;
import io.fabric8.kubernetes.api.model.HasMetadata;
-import io.fabric8.kubernetes.api.model.KubernetesResourceList;
import io.fabric8.kubernetes.client.KubernetesClientException;
import io.fabric8.kubernetes.client.dsl.FilterWatchListDeletable;
import io.fabric8.kubernetes.client.informers.ResourceEventHandler;
import io.fabric8.kubernetes.client.informers.SharedIndexInformer;
+import io.fabric8.kubernetes.client.utils.Serialization;
import io.javaoperatorsdk.operator.MissingCRDException;
import io.javaoperatorsdk.operator.api.config.ConfigurationService;
import io.javaoperatorsdk.operator.api.config.ControllerConfiguration;
@@ -29,19 +31,20 @@
public class ControllerResourceEventSource
extends AbstractResourceEventSource
- implements ResourceEventHandler {
+ implements ResourceEventHandler {
public static final String ANY_NAMESPACE_MAP_KEY = "anyNamespace";
private static final Logger log = LoggerFactory.getLogger(ControllerResourceEventSource.class);
private final Controller controller;
- private final Map> sharedIndexInformers =
+ private final Map> sharedIndexInformers =
new ConcurrentHashMap<>();
private final ResourceEventFilter filter;
private final OnceWhitelistEventFilterEventFilter onceWhitelistEventFilterEventFilter;
private final ControllerResourceCache cache;
+ private final String CRVersion;
public ControllerResourceEventSource(Controller controller) {
super(controller.getConfiguration().getResourceClass());
@@ -49,7 +52,7 @@ public ControllerResourceEventSource(Controller controller) {
final var configurationService = controller.getConfiguration().getConfigurationService();
var cloner = configurationService != null ? configurationService.getResourceCloner()
: ConfigurationService.DEFAULT_CLONER;
- this.cache = new ControllerResourceCache<>(sharedIndexInformers, cloner);
+ this.cache = new ControllerResourceCache(sharedIndexInformers, cloner);
var filters = new ResourceEventFilter[] {
ResourceEventFilters.finalizerNeededAndApplied(),
@@ -69,13 +72,18 @@ public ControllerResourceEventSource(Controller controller) {
} else {
filter = ResourceEventFilters.or(filters);
}
+
+ var resourceClass = controller.getConfiguration().getResourceClass();
+ // TODO: check if we should use: HasMetadata.getFullResourceName(resourceClass);
+ this.CRVersion =
+ HasMetadata.getGroup(resourceClass) + "/" + HasMetadata.getVersion(resourceClass);
}
@Override
public void start() {
final var configuration = controller.getConfiguration();
final var targetNamespaces = configuration.getEffectiveNamespaces();
- final var client = controller.getCRClient();
+ final var client = controller.getGenericClient();
final var labelSelector = configuration.getLabelSelector();
try {
@@ -100,8 +108,9 @@ public void start() {
super.start();
}
- private SharedIndexInformer createAndRunInformerFor(
- FilterWatchListDeletable> filteredBySelectorClient, String key) {
+ private SharedIndexInformer createAndRunInformerFor(
+ FilterWatchListDeletable filteredBySelectorClient,
+ String key) {
var informer = filteredBySelectorClient.runnableInformer(0);
informer.addEventHandler(this);
sharedIndexInformers.put(key, informer);
@@ -111,7 +120,7 @@ private SharedIndexInformer createAndRunInformerFor(
@Override
public void stop() {
- for (SharedIndexInformer informer : sharedIndexInformers.values()) {
+ for (SharedIndexInformer informer : sharedIndexInformers.values()) {
try {
log.info("Stopping informer {} -> {}", controller, informer);
informer.stop();
@@ -143,19 +152,49 @@ public void eventReceived(ResourceAction action, T customResource, T oldResource
}
}
+ private String extractUnderlyingCRVersion(GenericKubernetesResource resource) {
+ return resource
+ .getMetadata()
+ .getManagedFields()
+ .get(0)
+ .getApiVersion();
+ }
+
@Override
- public void onAdd(T resource) {
- eventReceived(ResourceAction.ADDED, resource, null);
+ public void onAdd(GenericKubernetesResource genericKubernetesResource) {
+ if (CRVersion.equals(extractUnderlyingCRVersion(genericKubernetesResource))) {
+ var resource = Serialization.unmarshal(
+ Serialization.asJson(genericKubernetesResource), this.getResourceClass());
+ eventReceived(ResourceAction.ADDED, resource, null);
+ }
}
@Override
- public void onUpdate(T oldCustomResource, T newCustomResource) {
- eventReceived(ResourceAction.UPDATED, newCustomResource, oldCustomResource);
+ public void onUpdate(GenericKubernetesResource oldResource,
+ GenericKubernetesResource newResource) {
+ if (CRVersion.equals(extractUnderlyingCRVersion(newResource))) {
+ var newCustomResource = Serialization.unmarshal(
+ Serialization.asJson(newResource), this.getResourceClass());
+
+ // Best effort try to deserialize the old CR with the same deserializer as the new
+ T oldCustomResource = null;
+ try {
+ oldCustomResource = Serialization.unmarshal(
+ Serialization.asJson(newResource), this.getResourceClass());
+ } catch (Exception e) {
+ // ignored
+ }
+ eventReceived(ResourceAction.UPDATED, newCustomResource, oldCustomResource);
+ }
}
@Override
- public void onDelete(T resource, boolean b) {
- eventReceived(ResourceAction.DELETED, resource, null);
+ public void onDelete(GenericKubernetesResource genericKubernetesResource, boolean b) {
+ if (CRVersion.equals(extractUnderlyingCRVersion(genericKubernetesResource))) {
+ var resource = Serialization.unmarshal(
+ Serialization.asJson(genericKubernetesResource), this.getResourceClass());
+ eventReceived(ResourceAction.DELETED, resource, null);
+ }
}
public Optional get(ResourceID resourceID) {
@@ -170,11 +209,11 @@ public ControllerResourceCache getResourceCache() {
* @return shared informers by namespace. If custom resource is not namespace scoped use
* CustomResourceEventSource.ANY_NAMESPACE_MAP_KEY
*/
- public Map> getInformers() {
+ public Map> getInformers() {
return Collections.unmodifiableMap(sharedIndexInformers);
}
- public SharedIndexInformer getInformer(String namespace) {
+ public SharedIndexInformer getInformer(String namespace) {
return getInformers().get(Objects.requireNonNullElse(namespace, ANY_NAMESPACE_MAP_KEY));
}
@@ -206,4 +245,5 @@ private void handleKubernetesClientException(Exception e) {
public Optional getAssociated(T primary) {
return cache.get(ResourceID.fromResource(primary));
}
+
}
diff --git a/pom.xml b/pom.xml
index 22bc63c36b..7ce7522983 100644
--- a/pom.xml
+++ b/pom.xml
@@ -43,7 +43,7 @@
https://sonarcloud.io
5.8.2
- 5.11.2
+ 6.0-SNAPSHOT
1.7.33
2.17.1
4.2.0