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 initTasks, + ContainerImageInfoBuildItem image, + List initTasks, + Optional> disableInitJobs, BuildProducer jobs, BuildProducer initContainers, BuildProducer env, @@ -35,30 +38,30 @@ static void process( BuildProducer decorators) { List 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> 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> 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 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 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/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 kubernetesList = DeserializationUtil + .deserializeAsList(kubernetesDir.resolve("kubernetes.yml")); + + Optional 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 = 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 = kubernetesList.stream().filter( + r -> r instanceof RoleBinding && (NAME + "-view-jobs").equals(r.getMetadata().getName())) + .map(r -> (RoleBinding) r).findFirst(); + assertFalse(roleBinding.isPresent()); + } +}