From 76842a28af2769f0526d0f32d0d7159852208d1a Mon Sep 17 00:00:00 2001 From: Ioannis Canellos Date: Tue, 12 Sep 2023 15:36:36 +0300 Subject: [PATCH] feat: add init-task-defaults to k8s and openshift config --- docs/src/main/asciidoc/flyway.adoc | 23 +++++- docs/src/main/asciidoc/liquibase-mongodb.adoc | 23 +++++- docs/src/main/asciidoc/liquibase.adoc | 25 +++++- .../kubernetes/deployment/InitTaskConfig.java | 12 ++- .../deployment/InitTaskProcessor.java | 8 +- .../deployment/KubernetesConfig.java | 9 +++ .../deployment/OpenshiftConfig.java | 9 +++ .../deployment/OpenshiftProcessor.java | 2 +- .../VanillaKubernetesProcessor.java | 2 +- ...tesInitTaskWithCustomWaitForImageTest.java | 78 +++++++++++++++++++ 10 files changed, 178 insertions(+), 13 deletions(-) create mode 100644 integration-tests/kubernetes/quarkus-standard-way/src/test/java/io/quarkus/it/kubernetes/KubernetesInitTaskWithCustomWaitForImageTest.java diff --git a/docs/src/main/asciidoc/flyway.adoc b/docs/src/main/asciidoc/flyway.adoc index 27ce233f27c3a..c622c76cf69e1 100644 --- a/docs/src/main/asciidoc/flyway.adoc +++ b/docs/src/main/asciidoc/flyway.adoc @@ -290,18 +290,37 @@ once and then start the actual application without Flyway. To support this use c the generated manifests contain a Kubernetes initialization `Job` for Flyway. The `Job` performs initialization and the actual `Pod`, will starts once the `Job` is successfully completed. +=== Disabling + The feature is enabled by default and can be globally disabled, using: [source,properties] ---- -quarkus.kubernetes.externalize-init=false +quarkus.kubernetes.init-task-defaults.enabled=false ---- or on OpenShift: [source,properties] ---- -quarkus.openshift.externalize-init=false +quarkus.openshift.init-task-defaults.enabled=false +---- + +=== Using a custom image that controls waiting for the Job + +To change the `wait-for` image which by default is `groundnuty/k8s-wait-for:no-root-v1.7` you can use: + +[source,properties] +---- +quarkus.kubernetes.init-task-defaults.wait-for-image=my/wait-for-image:1.0 +---- + +or on Openshift: + + +[source,properties] +---- +quarkus.openshift.init-task-defaults.wait-for-image=my/wait-for-image:1.0 ---- **Note**: In this context globally means `for all extensions that support init task externalization`. diff --git a/docs/src/main/asciidoc/liquibase-mongodb.adoc b/docs/src/main/asciidoc/liquibase-mongodb.adoc index 9e92484258369..ac1162559ac35 100644 --- a/docs/src/main/asciidoc/liquibase-mongodb.adoc +++ b/docs/src/main/asciidoc/liquibase-mongodb.adoc @@ -159,18 +159,37 @@ once and then start the actual application without Liquibase. To support this us the generated manifests contain a Kubernetes initialization `Job` for Liquibase. The `Job` performs initialization and the actual `Pod`, will starts once the `Job` is successfully completed. +=== Disabling + The feature is enabled by default and can be globally disabled, using: [source,properties] ---- -quarkus.kubernetes.externalize-init=false +quarkus.kubernetes.init-task-defaults.enabled=false ---- or on OpenShift: [source,properties] ---- -quarkus.openshift.externalize-init=false +quarkus.openshift.init-task-defaults.enabled=false +---- + +=== Using a custom image that controls waiting for the Job + +To change the `wait-for` image which by default is `groundnuty/k8s-wait-for:no-root-v1.7` you can use: + +[source,properties] +---- +quarkus.kubernetes.init-task-defaults.wait-for-image=my/wait-for-image:1.0 +---- + +or on Openshift: + + +[source,properties] +---- +quarkus.openshift.init-task-defaults.wait-for-image=my/wait-for-image:1.0 ---- **Note**: In this context globally means `for all extensions that support init task externalization`. diff --git a/docs/src/main/asciidoc/liquibase.adoc b/docs/src/main/asciidoc/liquibase.adoc index c3249f9acd782..428d5a23fd6ea 100644 --- a/docs/src/main/asciidoc/liquibase.adoc +++ b/docs/src/main/asciidoc/liquibase.adoc @@ -237,20 +237,41 @@ once and then start the actual application without Liquibase. To support this us the generated manifests contain a Kubernetes initialization `Job` for Liquibase. The `Job` performs initialization and the actual `Pod`, will starts once the `Job` is successfully completed. +=== Disabling + The feature is enabled by default and can be globally disabled, using: [source,properties] ---- -quarkus.kubernetes.externalize-init=false +quarkus.kubernetes.init-task-defaults.enabled=false ---- or on OpenShift: [source,properties] ---- -quarkus.openshift.externalize-init=false +quarkus.openshift.init-task-defaults.enabled=false +---- + +=== Using a custom image that controls waiting for the Job + +To change the `wait-for` image which by default is `groundnuty/k8s-wait-for:no-root-v1.7` you can use: + +[source,properties] +---- +quarkus.kubernetes.init-task-defaults.wait-for-image=my/wait-for-image:1.0 ---- +or on Openshift: + + +[source,properties] +---- +quarkus.openshift.init-task-defaults.wait-for-image=my/wait-for-image:1.0 +---- + + + **Note**: In this context globally means `for all extensions that support init task externalization`. == Configuration Reference diff --git a/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/InitTaskConfig.java b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/InitTaskConfig.java index f139d18963179..e1a295726258a 100644 --- a/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/InitTaskConfig.java +++ b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/InitTaskConfig.java @@ -1,5 +1,7 @@ package io.quarkus.kubernetes.deployment; +import java.util.Optional; + import io.quarkus.runtime.annotations.ConfigGroup; import io.quarkus.runtime.annotations.ConfigItem; @@ -11,9 +13,17 @@ public class InitTaskConfig { @ConfigItem(defaultValue = "true") public boolean enabled; + /** + * The init task image to use by the init-container. + */ + @Deprecated + @ConfigItem + public Optional image; + /** * The init task image to use by the init-container. */ @ConfigItem(defaultValue = "groundnuty/k8s-wait-for:no-root-v1.7") - public String image; + public String waitForImage; + } 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 58fb10d804e16..3d7be4b9fe4ba 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 @@ -22,13 +22,13 @@ public class InitTaskProcessor { private static final String INIT_CONTAINER_WAITER_NAME = "init"; - private static final String INIT_CONTAINER_WAITER_DEFAULT_IMAGE = "groundnuty/k8s-wait-for:no-root-v1.7"; static void process( String target, // kubernetes, openshift, etc. String name, ContainerImageInfoBuildItem image, List initTasks, + InitTaskConfig initTaskDefaults, Map initTasksConfig, BuildProducer jobs, BuildProducer initContainers, @@ -40,7 +40,7 @@ static void process( boolean generateRoleForJobs = false; for (InitTaskBuildItem task : initTasks) { - InitTaskConfig config = initTasksConfig.get(task.getName()); + InitTaskConfig config = initTasksConfig.getOrDefault(task.getName(), initTaskDefaults); if (config == null || config.enabled) { generateRoleForJobs = true; jobs.produce(KubernetesJobBuildItem.create(image.getImage()) @@ -60,8 +60,8 @@ static void process( .build()))); }); - initContainers.produce(KubernetesInitContainerBuildItem.create(INIT_CONTAINER_WAITER_NAME, - config == null ? INIT_CONTAINER_WAITER_DEFAULT_IMAGE : config.image) + String waitForImage = config.image.orElse(config.waitForImage); + initContainers.produce(KubernetesInitContainerBuildItem.create(INIT_CONTAINER_WAITER_NAME, waitForImage) .withTarget(target) .withArguments(List.of("job", task.getName()))); } 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 cd31abd1d669f..999d9c6b0f5a1 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 @@ -381,6 +381,15 @@ public enum DeploymentResourceKind { @ConfigItem Map initTasks; + /** + * Default Init tasks configuration. + * + * The init tasks are automatically generated by extensions like Flyway to perform the database migration before staring + * up the application. + */ + @ConfigItem + InitTaskConfig initTaskDefaults; + /** * 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 3c3062eb7f3eb..a4ed700237fcf 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 @@ -598,6 +598,15 @@ public EnvVarsConfig getEnv() { @ConfigItem Map initTasks; + /** + * Default Init tasks configuration. + * + * The init tasks are automatically generated by extensions like Flyway to perform the database migration before staring + * up the application. + */ + @ConfigItem + InitTaskConfig initTaskDefaults; + /** * 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 7c6f38b8697fc..6e8747568b32a 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 @@ -406,7 +406,7 @@ void externalizeInitTasks( BuildProducer decorators) { final String name = ResourceNameUtil.getResourceName(config, applicationInfo); if (config.externalizeInit) { - InitTaskProcessor.process(OPENSHIFT, name, image, initTasks, config.initTasks, + InitTaskProcessor.process(OPENSHIFT, name, image, initTasks, config.initTaskDefaults, config.initTasks, jobs, initContainers, env, roles, roleBindings, serviceAccount, 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 1e1e904a25acd..04d6f0c5d1859 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 @@ -319,7 +319,7 @@ void externalizeInitTasks( BuildProducer decorators) { final String name = ResourceNameUtil.getResourceName(config, applicationInfo); if (config.externalizeInit) { - InitTaskProcessor.process(KUBERNETES, name, image, initTasks, config.initTasks, + InitTaskProcessor.process(KUBERNETES, name, image, initTasks, config.initTaskDefaults, config.initTasks, jobs, initContainers, env, roles, roleBindings, serviceAccount, decorators); } } diff --git a/integration-tests/kubernetes/quarkus-standard-way/src/test/java/io/quarkus/it/kubernetes/KubernetesInitTaskWithCustomWaitForImageTest.java b/integration-tests/kubernetes/quarkus-standard-way/src/test/java/io/quarkus/it/kubernetes/KubernetesInitTaskWithCustomWaitForImageTest.java new file mode 100644 index 0000000000000..b1926be6897fe --- /dev/null +++ b/integration-tests/kubernetes/quarkus-standard-way/src/test/java/io/quarkus/it/kubernetes/KubernetesInitTaskWithCustomWaitForImageTest.java @@ -0,0 +1,78 @@ +package io.quarkus.it.kubernetes; + +import static org.assertj.core.api.Assertions.assertThat; +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.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 KubernetesInitTaskWithCustomWaitForImageTest { + + private static final String NAME = "kubernetes-custom-wait-for"; + private static final String WAIT_FOR_IMAGE = "my/awesome-wait-for-image"; + + @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.init-task-defaults.wait-for-image", WAIT_FOR_IMAGE) + .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()).singleElement().satisfies(container -> { + assertThat(container.getName()).isEqualTo("init"); + assertThat(container.getImage()).isEqualTo(WAIT_FOR_IMAGE); + }); + + }); + }); + }); + }); + } +}