From 6b34b3cbfd8a09af867f4925f48c2c8abd167a46 Mon Sep 17 00:00:00 2001
From: Jose <josecarvajalhilario@gmail.com>
Date: Tue, 16 May 2023 13:30:02 +0200
Subject: [PATCH] Add `disable-init-tasks` property to disable generation of
 tasks by name

Fix https://github.com/quarkusio/quarkus/issues/33097
---
 .../deployment/InitTaskProcessor.java         | 48 +++++-----
 .../deployment/KubernetesConfig.java          | 11 ++-
 .../deployment/OpenshiftConfig.java           |  9 ++
 .../deployment/OpenshiftProcessor.java        |  4 +-
 .../VanillaKubernetesProcessor.java           |  4 +-
 ...WithFlywayInitOverrideWaiterImageTest.java |  3 +-
 ...etesWithFlywayInitWithJobDisabledTest.java | 89 +++++++++++++++++++
 7 files changed, 139 insertions(+), 29 deletions(-)
 create mode 100644 integration-tests/kubernetes/quarkus-standard-way/src/test/java/io/quarkus/it/kubernetes/KubernetesWithFlywayInitWithJobDisabledTest.java

diff --git a/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/InitTaskProcessor.java b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/InitTaskProcessor.java
index 64dc6308db7cc4..8e555059da729f 100644
--- a/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/InitTaskProcessor.java
+++ b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/InitTaskProcessor.java
@@ -3,6 +3,7 @@
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
+import java.util.Optional;
 
 import io.dekorate.kubernetes.config.EnvBuilder;
 import io.dekorate.kubernetes.decorator.AddEnvVarDecorator;
@@ -26,7 +27,9 @@ public class InitTaskProcessor {
     static void process(
             String target, // kubernetes, openshift, etc.
             String name,
-            ContainerImageInfoBuildItem image, List<InitTaskBuildItem> initTasks,
+            ContainerImageInfoBuildItem image,
+            List<InitTaskBuildItem> initTasks,
+            Optional<List<String>> disableInitJobs,
             BuildProducer<KubernetesJobBuildItem> jobs,
             BuildProducer<KubernetesInitContainerBuildItem> initContainers,
             BuildProducer<KubernetesEnvBuildItem> env,
@@ -35,30 +38,30 @@ static void process(
             BuildProducer<DecoratorBuildItem> decorators) {
 
         List<String> initContainerWaiterArgs = new ArrayList<>(initTasks.size() + 1);
-        initContainerWaiterArgs.add("job");
+        for (InitTaskBuildItem task : initTasks) {
+            if (disableInitJobs.isEmpty() || !disableInitJobs.get().contains(task.getName())) {
+                initContainerWaiterArgs.add(task.getName());
+                jobs.produce(KubernetesJobBuildItem.create(image.getImage())
+                        .withName(task.getName())
+                        .withTarget(target)
+                        .withEnvVars(task.getTaskEnvVars())
+                        .withCommand(task.getCommand())
+                        .withArguments(task.getArguments())
+                        .withSharedEnvironment(task.isSharedEnvironment())
+                        .withSharedFilesystem(task.isSharedFilesystem()));
 
-        initTasks.forEach(task -> {
-            initContainerWaiterArgs.add(task.getName());
-            jobs.produce(KubernetesJobBuildItem.create(image.getImage())
-                    .withName(task.getName())
-                    .withTarget(target)
-                    .withEnvVars(task.getTaskEnvVars())
-                    .withCommand(task.getCommand())
-                    .withArguments(task.getArguments())
-                    .withSharedEnvironment(task.isSharedEnvironment())
-                    .withSharedFilesystem(task.isSharedFilesystem()));
-
-            task.getAppEnvVars().forEach((k, v) -> {
-                decorators.produce(new DecoratorBuildItem(target,
-                        new AddEnvVarDecorator(ApplicationContainerDecorator.ANY, name, new EnvBuilder()
-                                .withName(k)
-                                .withValue(v)
-                                .build())));
+                task.getAppEnvVars().forEach((k, v) -> {
+                    decorators.produce(new DecoratorBuildItem(target,
+                            new AddEnvVarDecorator(ApplicationContainerDecorator.ANY, name, new EnvBuilder()
+                                    .withName(k)
+                                    .withValue(v)
+                                    .build())));
 
-            });
-        });
+                });
+            }
+        }
 
-        if (!initTasks.isEmpty()) {
+        if (!initContainerWaiterArgs.isEmpty()) {
             roles.produce(new KubernetesRoleBuildItem("view-jobs", Collections.singletonList(
                     new PolicyRule(
                             Collections.singletonList("batch"),
@@ -67,6 +70,7 @@ static void process(
                     target));
             roleBindings.produce(new KubernetesRoleBindingBuildItem(null, "view-jobs", false, target));
 
+            initContainerWaiterArgs.add(0, "job");
             initContainers.produce(KubernetesInitContainerBuildItem.create(INIT_CONTAINER_WAITER_NAME,
                     INIT_CONTAINER_WAITER_DEFAULT_IMAGE)
                     .withTarget(target)
diff --git a/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/KubernetesConfig.java b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/KubernetesConfig.java
index d5fd0a1782ad79..ad4ba4d07a07cc 100644
--- a/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/KubernetesConfig.java
+++ b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/KubernetesConfig.java
@@ -340,11 +340,20 @@ public enum DeploymentResourceKind {
      * Flag to enable init task externalization.
      * When enabled (default), all initialization tasks
      * created by extensions, will be externalized as Jobs.
-     * In addition the deployment will wait for these jobs.
+     * In addition, the deployment will wait for these jobs.
      */
     @ConfigItem(defaultValue = "true")
     boolean externalizeInit;
 
+    /**
+     * List of init tasks to be disabled and hence not to be generated. The init tasks are automatically generated by
+     * extensions like Flyway to perform the database migration before staring up the application.
+     *
+     * This property is only taken into account if `quarkus.kubernetes.externalize-init` is true.
+     */
+    @ConfigItem
+    Optional<List<String>> disableInitTasks;
+
     /**
      * Switch used to control whether non-idempotent fields are included in generated kubernetes resources to improve
      * git-ops compatibility
diff --git a/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/OpenshiftConfig.java b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/OpenshiftConfig.java
index 6f731816ac42a2..5d6bada25d4f75 100644
--- a/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/OpenshiftConfig.java
+++ b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/OpenshiftConfig.java
@@ -573,6 +573,15 @@ public EnvVarsConfig getEnv() {
     @ConfigItem(defaultValue = "true")
     boolean externalizeInit;
 
+    /**
+     * List of init tasks to be disabled and hence not to be generated. The init tasks are automatically generated by
+     * extensions like Flyway to perform the database migration before staring up the application.
+     *
+     * This property is only taken into account if `quarkus.openshift.externalize-init` is true.
+     */
+    @ConfigItem
+    Optional<List<String>> disableInitTasks;
+
     /**
      * Switch used to control whether non-idempotent fields are included in generated kubernetes resources to improve
      * git-ops compatibility
diff --git a/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/OpenshiftProcessor.java b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/OpenshiftProcessor.java
index 1f180f8d31bf17..76311a8eceb8d9 100644
--- a/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/OpenshiftProcessor.java
+++ b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/OpenshiftProcessor.java
@@ -384,8 +384,8 @@ void externalizeInitTasks(
             BuildProducer<DecoratorBuildItem> decorators) {
         final String name = ResourceNameUtil.getResourceName(config, applicationInfo);
         if (config.externalizeInit) {
-            InitTaskProcessor.process(OPENSHIFT, name, image, initTasks, jobs, initContainers, env, roles, roleBindings,
-                    decorators);
+            InitTaskProcessor.process(OPENSHIFT, name, image, initTasks, config.disableInitTasks,
+                    jobs, initContainers, env, roles, roleBindings, decorators);
         }
     }
 }
diff --git a/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/VanillaKubernetesProcessor.java b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/VanillaKubernetesProcessor.java
index ca88fe0eff3ecb..c467532539be3b 100644
--- a/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/VanillaKubernetesProcessor.java
+++ b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/VanillaKubernetesProcessor.java
@@ -293,8 +293,8 @@ void externalizeInitTasks(
             BuildProducer<DecoratorBuildItem> decorators) {
         final String name = ResourceNameUtil.getResourceName(config, applicationInfo);
         if (config.externalizeInit) {
-            InitTaskProcessor.process(KUBERNETES, name, image, initTasks, jobs, initContainers, env, roles, roleBindings,
-                    decorators);
+            InitTaskProcessor.process(KUBERNETES, name, image, initTasks, config.disableInitTasks,
+                    jobs, initContainers, env, roles, roleBindings, decorators);
         }
     }
 }
\ No newline at end of file
diff --git a/integration-tests/kubernetes/quarkus-standard-way/src/test/java/io/quarkus/it/kubernetes/KubernetesWithFlywayInitOverrideWaiterImageTest.java b/integration-tests/kubernetes/quarkus-standard-way/src/test/java/io/quarkus/it/kubernetes/KubernetesWithFlywayInitOverrideWaiterImageTest.java
index 77d5410d9984cd..4a86bedb920766 100644
--- a/integration-tests/kubernetes/quarkus-standard-way/src/test/java/io/quarkus/it/kubernetes/KubernetesWithFlywayInitOverrideWaiterImageTest.java
+++ b/integration-tests/kubernetes/quarkus-standard-way/src/test/java/io/quarkus/it/kubernetes/KubernetesWithFlywayInitOverrideWaiterImageTest.java
@@ -35,7 +35,6 @@ public class KubernetesWithFlywayInitOverrideWaiterImageTest {
             .setApplicationName(NAME)
             .setApplicationVersion("0.1-SNAPSHOT")
             .setLogFileName("k8s.log")
-            .overrideConfigKey("quarkus.flyway.migrate-at-start", "true")
             .overrideConfigKey("quarkus.kubernetes.init-containers.\"init\".image", OVERRIDDEN_WAITER_IMAGE)
             .setForcedDependencies(Arrays.asList(
                     new AppArtifact("io.quarkus", "quarkus-kubernetes", Version.getVersion()),
@@ -107,7 +106,7 @@ public void assertGeneratedResources() throws IOException {
         });
 
         Optional<RoleBinding> roleBinding = kubernetesList.stream().filter(
-                        r -> r instanceof RoleBinding && (NAME + "-view-jobs").equals(r.getMetadata().getName()))
+                r -> r instanceof RoleBinding && (NAME + "-view-jobs").equals(r.getMetadata().getName()))
                 .map(r -> (RoleBinding) r).findFirst();
         assertTrue(roleBinding.isPresent());
     }
diff --git a/integration-tests/kubernetes/quarkus-standard-way/src/test/java/io/quarkus/it/kubernetes/KubernetesWithFlywayInitWithJobDisabledTest.java b/integration-tests/kubernetes/quarkus-standard-way/src/test/java/io/quarkus/it/kubernetes/KubernetesWithFlywayInitWithJobDisabledTest.java
new file mode 100644
index 00000000000000..1cbd40f0faa839
--- /dev/null
+++ b/integration-tests/kubernetes/quarkus-standard-way/src/test/java/io/quarkus/it/kubernetes/KubernetesWithFlywayInitWithJobDisabledTest.java
@@ -0,0 +1,89 @@
+package io.quarkus.it.kubernetes;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Optional;
+
+import org.jboss.shrinkwrap.api.ShrinkWrap;
+import org.jboss.shrinkwrap.api.spec.JavaArchive;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+import io.fabric8.kubernetes.api.model.HasMetadata;
+import io.fabric8.kubernetes.api.model.apps.Deployment;
+import io.fabric8.kubernetes.api.model.batch.v1.Job;
+import io.fabric8.kubernetes.api.model.rbac.RoleBinding;
+import io.quarkus.bootstrap.model.AppArtifact;
+import io.quarkus.builder.Version;
+import io.quarkus.test.ProdBuildResults;
+import io.quarkus.test.ProdModeTestResults;
+import io.quarkus.test.QuarkusProdModeTest;
+
+public class KubernetesWithFlywayInitWithJobDisabledTest {
+
+    private static final String NAME = "kubernetes-with-flyway-with-job-disabled";
+
+    @RegisterExtension
+    static final QuarkusProdModeTest config = new QuarkusProdModeTest()
+            .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class).addClasses(GreetingResource.class))
+            .setApplicationName(NAME)
+            .setApplicationVersion("0.1-SNAPSHOT")
+            .setLogFileName("k8s.log")
+            .overrideConfigKey("quarkus.kubernetes.disable-init-tasks", NAME + "-flyway-init")
+            .setForcedDependencies(Arrays.asList(
+                    new AppArtifact("io.quarkus", "quarkus-kubernetes", Version.getVersion()),
+                    new AppArtifact("io.quarkus", "quarkus-flyway", Version.getVersion())));
+
+    @ProdBuildResults
+    private ProdModeTestResults prodModeTestResults;
+
+    @Test
+    public void assertGeneratedResources() throws IOException {
+        final Path kubernetesDir = prodModeTestResults.getBuildDir().resolve("kubernetes");
+        assertThat(kubernetesDir)
+                .isDirectoryContaining(p -> p.getFileName().endsWith("kubernetes.json"))
+                .isDirectoryContaining(p -> p.getFileName().endsWith("kubernetes.yml"));
+        List<HasMetadata> kubernetesList = DeserializationUtil
+                .deserializeAsList(kubernetesDir.resolve("kubernetes.yml"));
+
+        Optional<Deployment> deployment = kubernetesList.stream()
+                .filter(d -> "Deployment".equals(d.getKind())
+                        && NAME.equals(d.getMetadata().getName()))
+                .map(d -> (Deployment) d).findAny();
+
+        assertTrue(deployment.isPresent());
+        assertThat(deployment).satisfies(j -> j.isPresent());
+        assertThat(deployment.get()).satisfies(d -> {
+            assertThat(d.getMetadata()).satisfies(m -> {
+                assertThat(m.getName()).isEqualTo(NAME);
+            });
+
+            assertThat(d.getSpec()).satisfies(deploymentSpec -> {
+                assertThat(deploymentSpec.getTemplate()).satisfies(t -> {
+                    assertThat(t.getSpec()).satisfies(podSpec -> {
+                        assertThat(podSpec.getInitContainers()).noneSatisfy(container -> {
+                            assertThat(container.getName()).isEqualTo("init");
+                        });
+                    });
+                });
+            });
+        });
+
+        Optional<Job> job = kubernetesList.stream()
+                .filter(j -> "Job".equals(j.getKind()) && (NAME + "-flyway-init").equals(j.getMetadata().getName()))
+                .map(j -> (Job) j)
+                .findAny();
+        assertFalse(job.isPresent());
+
+        Optional<RoleBinding> roleBinding = kubernetesList.stream().filter(
+                r -> r instanceof RoleBinding && (NAME + "-view-jobs").equals(r.getMetadata().getName()))
+                .map(r -> (RoleBinding) r).findFirst();
+        assertFalse(roleBinding.isPresent());
+    }
+}