From 5ef6808c85d08fd24501382354f6b6b341a0f9cc Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Attila=20M=C3=A9sz=C3=A1ros?= <csviri@gmail.com>
Date: Thu, 7 Sep 2023 13:35:41 +0200
Subject: [PATCH] feat: configurable SSA per Dependent Resource
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Attila Mészáros <csviri@gmail.com>
---
 .../kubernetes/BooleanWithUndefined.java      | 19 +++++++++++++++++++
 .../kubernetes/KubernetesDependent.java       | 14 +++++++++++++-
 .../KubernetesDependentConverter.java         |  4 +++-
 .../KubernetesDependentResource.java          |  7 +++++--
 .../KubernetesDependentResourceConfig.java    | 10 +++++++++-
 5 files changed, 49 insertions(+), 5 deletions(-)
 create mode 100644 operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/BooleanWithUndefined.java

diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/BooleanWithUndefined.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/BooleanWithUndefined.java
new file mode 100644
index 0000000000..fcf7553a4a
--- /dev/null
+++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/BooleanWithUndefined.java
@@ -0,0 +1,19 @@
+package io.javaoperatorsdk.operator.processing.dependent.kubernetes;
+
+/**
+ * A replacement for {@link Boolean}, which can't be used in annotations.
+ */
+public enum BooleanWithUndefined {
+  TRUE, FALSE, UNDEFINED;
+
+  Boolean asBoolean() {
+    switch (this) {
+      case TRUE:
+        return Boolean.TRUE;
+      case FALSE:
+        return Boolean.FALSE;
+      default:
+        return null;
+    }
+  }
+}
diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/KubernetesDependent.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/KubernetesDependent.java
index c3f7be408a..eb4c9cf9b0 100644
--- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/KubernetesDependent.java
+++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/KubernetesDependent.java
@@ -25,7 +25,7 @@
    * namespace is specified then the controller will monitor the namespaces configured for the
    * controller.
    *
-   * @return the list of namespaces this controller monitors
+   * @return the array of namespaces this controller monitors
    */
   String[] namespaces() default {Constants.SAME_AS_CONTROLLER};
 
@@ -76,4 +76,16 @@
    * Creates the resource only if did not exist before, this applies only if SSA is used.
    */
   boolean createResourceOnlyIfNotExistingWithSSA() default KubernetesDependentResourceConfig.DEFAULT_CREATE_RESOURCE_ONLY_IF_NOT_EXISTING_WITH_SSA;
+
+  /**
+   * Determines whether to use SSA (Server-Side Apply) for this dependent. If SSA is used, the
+   * dependent resource will only be created if it did not exist before. Default value is
+   * {@link BooleanWithUndefined#UNDEFINED}, which specifies that the behavior with respect to SSA
+   * is inherited from the global configuration.
+   *
+   * @return {@code true} if SSA is enabled, {@code false} if SSA is disabled,
+   *         {@link BooleanWithUndefined#UNDEFINED} if the SSA behavior should be inherited from the
+   *         global configuration
+   */
+  BooleanWithUndefined useSSA() default BooleanWithUndefined.UNDEFINED;
 }
diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/KubernetesDependentConverter.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/KubernetesDependentConverter.java
index 6ab07a9462..7a434aecf1 100644
--- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/KubernetesDependentConverter.java
+++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/KubernetesDependentConverter.java
@@ -34,6 +34,7 @@ public KubernetesDependentResourceConfig<R> configFrom(KubernetesDependent confi
     OnDeleteFilter<? extends HasMetadata> onDeleteFilter = null;
     GenericFilter<? extends HasMetadata> genericFilter = null;
     ResourceDiscriminator<?, ?> resourceDiscriminator = null;
+    Boolean useSSA = null;
     if (configAnnotation != null) {
       if (!Arrays.equals(KubernetesDependent.DEFAULT_NAMESPACES, configAnnotation.namespaces())) {
         namespaces = Set.of(configAnnotation.namespaces());
@@ -58,10 +59,11 @@ public KubernetesDependentResourceConfig<R> configFrom(KubernetesDependent confi
               context);
       createResourceOnlyIfNotExistingWithSSA =
           configAnnotation.createResourceOnlyIfNotExistingWithSSA();
+      useSSA = configAnnotation.useSSA().asBoolean();
     }
 
     return new KubernetesDependentResourceConfig(namespaces, labelSelector, configuredNS,
         createResourceOnlyIfNotExistingWithSSA,
-        resourceDiscriminator, onAddFilter, onUpdateFilter, onDeleteFilter, genericFilter);
+        resourceDiscriminator, useSSA, onAddFilter, onUpdateFilter, onDeleteFilter, genericFilter);
   }
 }
diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/KubernetesDependentResource.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/KubernetesDependentResource.java
index 5fedd0899d..7fadcc6940 100644
--- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/KubernetesDependentResource.java
+++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/KubernetesDependentResource.java
@@ -190,8 +190,10 @@ protected void addMetadata(boolean forMatch, R actualResource, final R target, P
   }
 
   private boolean useSSA(Context<P> context) {
-    return context.getControllerConfiguration().getConfigurationService()
-        .ssaBasedCreateUpdateMatchForDependentResources();
+    Optional<Boolean> useSSAConfig =
+        configuration().flatMap(KubernetesDependentResourceConfig::useSSA);
+    return useSSAConfig.orElse(context.getControllerConfiguration().getConfigurationService()
+        .ssaBasedCreateUpdateMatchForDependentResources());
   }
 
   @Override
@@ -206,6 +208,7 @@ public void deleteTargetResource(P primary, R resource, String key, Context<P> c
     client.resource(resource).delete();
   }
 
+  @SuppressWarnings("unused")
   protected Resource<R> prepare(R desired, P primary, String actionName) {
     log.debug("{} target resource with type: {}, with id: {}",
         actionName,
diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/KubernetesDependentResourceConfig.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/KubernetesDependentResourceConfig.java
index 33f4f91d1f..836bfbfc08 100644
--- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/KubernetesDependentResourceConfig.java
+++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/KubernetesDependentResourceConfig.java
@@ -1,5 +1,6 @@
 package io.javaoperatorsdk.operator.processing.dependent.kubernetes;
 
+import java.util.Optional;
 import java.util.Set;
 
 import io.javaoperatorsdk.operator.api.reconciler.Constants;
@@ -20,6 +21,7 @@ public class KubernetesDependentResourceConfig<R> {
   private boolean namespacesWereConfigured = false;
   private boolean createResourceOnlyIfNotExistingWithSSA;
   private ResourceDiscriminator<R, ?> resourceDiscriminator;
+  private Boolean useSSA;
 
   private OnAddFilter<R> onAddFilter;
 
@@ -36,6 +38,7 @@ public KubernetesDependentResourceConfig(Set<String> namespaces,
       boolean configuredNS,
       boolean createResourceOnlyIfNotExistingWithSSA,
       ResourceDiscriminator<R, ?> resourceDiscriminator,
+      Boolean useSSA,
       OnAddFilter<R> onAddFilter,
       OnUpdateFilter<R> onUpdateFilter,
       OnDeleteFilter<R> onDeleteFilter, GenericFilter<R> genericFilter) {
@@ -48,12 +51,13 @@ public KubernetesDependentResourceConfig(Set<String> namespaces,
     this.onDeleteFilter = onDeleteFilter;
     this.genericFilter = genericFilter;
     this.resourceDiscriminator = resourceDiscriminator;
+    this.useSSA = useSSA;
   }
 
   public KubernetesDependentResourceConfig(Set<String> namespaces, String labelSelector) {
     this(namespaces, labelSelector, true, DEFAULT_CREATE_RESOURCE_ONLY_IF_NOT_EXISTING_WITH_SSA,
         null, null, null,
-        null, null);
+        null, null, null);
   }
 
   public KubernetesDependentResourceConfig<R> setLabelSelector(String labelSelector) {
@@ -105,4 +109,8 @@ protected void setNamespaces(Set<String> namespaces) {
       this.namespaces = namespaces;
     }
   }
+
+  public Optional<Boolean> useSSA() {
+    return Optional.ofNullable(useSSA);
+  }
 }