diff --git a/extensions/kubernetes/spi/src/main/java/io/quarkus/kubernetes/spi/KubernetesInitContainerBuildItem.java b/extensions/kubernetes/spi/src/main/java/io/quarkus/kubernetes/spi/KubernetesInitContainerBuildItem.java index e295485449a7d3..5b9b026f1fe9e5 100644 --- a/extensions/kubernetes/spi/src/main/java/io/quarkus/kubernetes/spi/KubernetesInitContainerBuildItem.java +++ b/extensions/kubernetes/spi/src/main/java/io/quarkus/kubernetes/spi/KubernetesInitContainerBuildItem.java @@ -24,8 +24,8 @@ public final class KubernetesInitContainerBuildItem extends MultiBuildItem { private final boolean sharedEnvironment; private final boolean sharedFilesystem; - public static KubernetesInitContainerBuildItem create(String image) { - return new KubernetesInitContainerBuildItem("init", null, image, Collections.emptyList(), Collections.emptyList(), + public static KubernetesInitContainerBuildItem create(String name, String image) { + return new KubernetesInitContainerBuildItem(name, null, image, Collections.emptyList(), Collections.emptyList(), Collections.emptyMap(), false, false); } diff --git a/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/AddInitContainerDecorator.java b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/AddInitContainerDecorator.java new file mode 100644 index 00000000000000..042567a06757f7 --- /dev/null +++ b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/AddInitContainerDecorator.java @@ -0,0 +1,74 @@ +package io.quarkus.kubernetes.deployment; + +import io.dekorate.kubernetes.adapter.ContainerAdapter; +import io.dekorate.kubernetes.config.Container; +import io.dekorate.kubernetes.decorator.NamedResourceDecorator; +import io.fabric8.kubernetes.api.model.ContainerBuilder; +import io.fabric8.kubernetes.api.model.ObjectMeta; +import io.fabric8.kubernetes.api.model.PodSpecBuilder; + +public abstract class AddInitContainerDecorator extends NamedResourceDecorator { + + private final Container container; + + public AddInitContainerDecorator(String deployment, Container container) { + super(deployment); + this.container = container; + } + + @Override + public void andThenVisit(PodSpecBuilder podSpec, ObjectMeta resourceMeta) { + var resource = ContainerAdapter.adapt(container); + if (podSpec.hasMatchingInitContainer(this::hasInitContainer)) { + update(podSpec, resource); + } else { + add(podSpec, resource); + } + } + + private void add(PodSpecBuilder podSpec, io.fabric8.kubernetes.api.model.Container resource) { + podSpec.addToInitContainers(resource); + } + + private void update(PodSpecBuilder podSpec, io.fabric8.kubernetes.api.model.Container resource) { + var matching = podSpec.editMatchingInitContainer(this::hasInitContainer); + if (resource.getImage() != null) { + matching.withImage(resource.getImage()); + } + + if (resource.getImage() != null) { + matching.withImage(resource.getImage()); + } + + if (resource.getWorkingDir() != null) { + matching.withWorkingDir(resource.getWorkingDir()); + } + + if (resource.getCommand() != null && !resource.getCommand().isEmpty()) { + matching.withCommand(resource.getCommand()); + } + + if (resource.getArgs() != null && !resource.getArgs().isEmpty()) { + matching.withArgs(resource.getArgs()); + } + + if (resource.getReadinessProbe() != null) { + matching.withReadinessProbe(resource.getReadinessProbe()); + } + + if (resource.getLivenessProbe() != null) { + matching.withLivenessProbe(resource.getLivenessProbe()); + } + + matching.addAllToEnv(resource.getEnv()); + if (resource.getPorts() != null && !resource.getPorts().isEmpty()) { + matching.withPorts(resource.getPorts()); + } + + matching.endInitContainer(); + } + + private boolean hasInitContainer(ContainerBuilder containerBuilder) { + return containerBuilder.getName().equals(container.getName()); + } +} diff --git a/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/AddInitContainerFromExtensionsDecorator.java b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/AddInitContainerFromExtensionsDecorator.java new file mode 100644 index 00000000000000..a7641413d3025f --- /dev/null +++ b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/AddInitContainerFromExtensionsDecorator.java @@ -0,0 +1,15 @@ +package io.quarkus.kubernetes.deployment; + +import io.dekorate.kubernetes.config.Container; +import io.dekorate.kubernetes.decorator.Decorator; + +public class AddInitContainerFromExtensionsDecorator extends AddInitContainerDecorator { + + public AddInitContainerFromExtensionsDecorator(String deployment, Container container) { + super(deployment, container); + } + + public Class[] before() { + return new Class[] { AddInitContainerFromUserConfigDecorator.class }; + } +} diff --git a/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/AddInitContainerFromUserConfigDecorator.java b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/AddInitContainerFromUserConfigDecorator.java new file mode 100644 index 00000000000000..482b434bc5e4a4 --- /dev/null +++ b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/AddInitContainerFromUserConfigDecorator.java @@ -0,0 +1,10 @@ +package io.quarkus.kubernetes.deployment; + +import io.dekorate.kubernetes.config.Container; + +public class AddInitContainerFromUserConfigDecorator extends AddInitContainerDecorator { + + public AddInitContainerFromUserConfigDecorator(String name, Container container) { + super(name, container); + } +} diff --git a/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/ApplyContainerImageDecorator.java b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/ApplyContainerImageDecorator.java index 6cdafae80faecd..faf946b6c830bf 100644 --- a/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/ApplyContainerImageDecorator.java +++ b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/ApplyContainerImageDecorator.java @@ -5,7 +5,6 @@ import io.dekorate.ConfigReference; import io.dekorate.WithConfigReferences; -import io.dekorate.kubernetes.decorator.AddInitContainerDecorator; import io.dekorate.kubernetes.decorator.AddSidecarDecorator; import io.dekorate.kubernetes.decorator.ApplicationContainerDecorator; import io.dekorate.kubernetes.decorator.ApplyImageDecorator; diff --git a/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/ChangeContainerNameDecorator.java b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/ChangeContainerNameDecorator.java index b8c7f7a1bc2b8e..cb05c79389e409 100644 --- a/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/ChangeContainerNameDecorator.java +++ b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/ChangeContainerNameDecorator.java @@ -1,7 +1,6 @@ package io.quarkus.kubernetes.deployment; import io.dekorate.kubernetes.decorator.AddEnvVarDecorator; -import io.dekorate.kubernetes.decorator.AddInitContainerDecorator; import io.dekorate.kubernetes.decorator.AddLivenessProbeDecorator; import io.dekorate.kubernetes.decorator.AddMountDecorator; import io.dekorate.kubernetes.decorator.AddPortDecorator; diff --git a/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/ChangeContainerNameInDeploymentTriggerDecorator.java b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/ChangeContainerNameInDeploymentTriggerDecorator.java index 8dcb7469505a52..6f698b32d327d0 100644 --- a/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/ChangeContainerNameInDeploymentTriggerDecorator.java +++ b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/ChangeContainerNameInDeploymentTriggerDecorator.java @@ -1,7 +1,23 @@ package io.quarkus.kubernetes.deployment; -import io.dekorate.kubernetes.decorator.*; +import io.dekorate.kubernetes.decorator.AddAwsElasticBlockStoreVolumeDecorator; +import io.dekorate.kubernetes.decorator.AddAzureDiskVolumeDecorator; +import io.dekorate.kubernetes.decorator.AddEnvVarDecorator; +import io.dekorate.kubernetes.decorator.AddLivenessProbeDecorator; +import io.dekorate.kubernetes.decorator.AddMountDecorator; +import io.dekorate.kubernetes.decorator.AddPortDecorator; +import io.dekorate.kubernetes.decorator.AddPvcVolumeDecorator; +import io.dekorate.kubernetes.decorator.AddReadinessProbeDecorator; import io.dekorate.kubernetes.decorator.AddSidecarDecorator; +import io.dekorate.kubernetes.decorator.ApplyApplicationContainerDecorator; +import io.dekorate.kubernetes.decorator.ApplyArgsDecorator; +import io.dekorate.kubernetes.decorator.ApplyCommandDecorator; +import io.dekorate.kubernetes.decorator.ApplyImageDecorator; +import io.dekorate.kubernetes.decorator.ApplyImagePullPolicyDecorator; +import io.dekorate.kubernetes.decorator.ApplyServiceAccountNamedDecorator; +import io.dekorate.kubernetes.decorator.ApplyWorkingDirDecorator; +import io.dekorate.kubernetes.decorator.Decorator; +import io.dekorate.kubernetes.decorator.NamedResourceDecorator; import io.dekorate.openshift.decorator.ApplyDeploymentTriggerDecorator; import io.fabric8.kubernetes.api.model.ObjectMeta; import io.fabric8.openshift.api.model.DeploymentConfigSpecFluent; 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 d99751102f5b4e..72572c429ff874 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 @@ -1,6 +1,6 @@ package io.quarkus.kubernetes.deployment; -import java.util.Arrays; +import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -20,10 +20,14 @@ 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:1.3"; + static void process( String target, // kubernetes, openshift, etc. String name, - ContainerImageInfoBuildItem image, List initTasks, + ContainerImageInfoBuildItem image, + List initTasks, BuildProducer jobs, BuildProducer initContainers, BuildProducer env, @@ -31,11 +35,11 @@ static void process( BuildProducer roleBindings, BuildProducer decorators) { - initTasks.forEach(task -> { - initContainers.produce(KubernetesInitContainerBuildItem.create("groundnuty/k8s-wait-for:1.3") - .withTarget(target) - .withArguments(Arrays.asList("job", task.getName()))); + List initContainerWaiterArgs = new ArrayList<>(initTasks.size() + 1); + initContainerWaiterArgs.add("job"); + initTasks.forEach(task -> { + initContainerWaiterArgs.add(task.getName()); jobs.produce(KubernetesJobBuildItem.create(image.getImage()) .withName(task.getName()) .withTarget(target) @@ -63,5 +67,12 @@ static void process( roleBindings.produce(new KubernetesRoleBindingBuildItem(null, "view-jobs", false, target)); }); + + if (!initTasks.isEmpty()) { + initContainers.produce(KubernetesInitContainerBuildItem.create(INIT_CONTAINER_WAITER_NAME, + INIT_CONTAINER_WAITER_DEFAULT_IMAGE) + .withTarget(target) + .withArguments(initContainerWaiterArgs)); + } } } diff --git a/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/KubernetesCommonHelper.java b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/KubernetesCommonHelper.java index 591ae9947c4a38..94460cedeb1867 100644 --- a/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/KubernetesCommonHelper.java +++ b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/KubernetesCommonHelper.java @@ -40,7 +40,6 @@ import io.dekorate.kubernetes.decorator.AddEnvVarDecorator; import io.dekorate.kubernetes.decorator.AddHostAliasesDecorator; import io.dekorate.kubernetes.decorator.AddImagePullSecretDecorator; -import io.dekorate.kubernetes.decorator.AddInitContainerDecorator; import io.dekorate.kubernetes.decorator.AddLabelDecorator; import io.dekorate.kubernetes.decorator.AddLivenessProbeDecorator; import io.dekorate.kubernetes.decorator.AddMetadataToTemplateDecorator; @@ -602,7 +601,7 @@ public void andThenVisit(ContainerBuilder builder) { } result.add(new DecoratorBuildItem(target, - new AddInitContainerDecorator(name, containerBuilder + new AddInitContainerFromExtensionsDecorator(name, containerBuilder .addAllToEnvVars(item.getEnvVars().entrySet().stream().map(e -> new EnvBuilder() .withName(e.getKey()) .withValue(e.getValue()) @@ -750,7 +749,8 @@ private static List createPodDecorators(Optional pr }); config.getInitContainers().entrySet().forEach(e -> { - result.add(new DecoratorBuildItem(target, new AddInitContainerDecorator(name, ContainerConverter.convert(e)))); + result.add(new DecoratorBuildItem(target, + new AddInitContainerFromUserConfigDecorator(name, ContainerConverter.convert(e)))); }); config.getSidecars().entrySet().forEach(e -> { diff --git a/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/MinikubeManifestGenerator.java b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/MinikubeManifestGenerator.java index 8c12db2e1558a2..f08aec0507d1cd 100644 --- a/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/MinikubeManifestGenerator.java +++ b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/MinikubeManifestGenerator.java @@ -17,7 +17,6 @@ import io.dekorate.kubernetes.config.KubernetesConfigBuilder; import io.dekorate.kubernetes.configurator.ApplyDeployToApplicationConfiguration; import io.dekorate.kubernetes.decorator.AddIngressDecorator; -import io.dekorate.kubernetes.decorator.AddInitContainerDecorator; import io.dekorate.kubernetes.decorator.AddServiceResourceDecorator; import io.dekorate.kubernetes.decorator.ApplyHeadlessDecorator; import io.dekorate.kubernetes.decorator.ApplyImageDecorator; @@ -108,7 +107,7 @@ protected void addDecorators(String group, KubernetesConfig config) { super.addDecorators(group, config); for (Container container : config.getInitContainers()) { - resourceRegistry.decorate(group, new AddInitContainerDecorator(config.getName(), container)); + resourceRegistry.decorate(group, new AddInitContainerFromUserConfigDecorator(config.getName(), container)); } if (config.getPorts().length > 0) { 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 new file mode 100644 index 00000000000000..a553fbaa4e7cb0 --- /dev/null +++ b/integration-tests/kubernetes/quarkus-standard-way/src/test/java/io/quarkus/it/kubernetes/KubernetesWithFlywayInitOverrideWaiterImageTest.java @@ -0,0 +1,112 @@ +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.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 KubernetesWithFlywayInitOverrideWaiterImageTest { + + private static final String NAME = "kubernetes-with-flyway-init-override-waiter-image"; + private static final String OVERRIDDEN_WAITER_IMAGE = "overridden:latest"; + + @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.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()), + 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(OVERRIDDEN_WAITER_IMAGE); + }); + + }); + }); + }); + }); + + Optional job = kubernetesList.stream() + .filter(j -> "Job".equals(j.getKind()) && "flyway-init".equals(j.getMetadata().getName())).map(j -> (Job) j) + .findAny(); + assertTrue(job.isPresent()); + + assertThat(job.get()).satisfies(j -> { + assertThat(j.getSpec()).satisfies(jobSpec -> { + assertThat(jobSpec.getTemplate()).satisfies(t -> { + assertThat(t.getSpec()).satisfies(podSpec -> { + assertThat(podSpec.getContainers()).singleElement().satisfies(container -> { + assertThat(container.getName()).isEqualTo("flyway-init"); + assertThat(container.getEnv()).filteredOn(env -> "QUARKUS_FLYWAY_ENABLED".equals(env.getName())) + .singleElement().satisfies(env -> { + assertThat(env.getValue()).isEqualTo("true"); + }); + assertThat(container.getEnv()) + .filteredOn(env -> "QUARKUS_INIT_AND_EXIT".equals(env.getName())).singleElement() + .satisfies(env -> { + assertThat(env.getValue()).isEqualTo("true"); + }); + }); + }); + }); + }); + }); + + Optional roleBinding = kubernetesList.stream().filter( + 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/KubernetesWithFlywayInitTest.java b/integration-tests/kubernetes/quarkus-standard-way/src/test/java/io/quarkus/it/kubernetes/KubernetesWithFlywayInitTest.java index c6bc3daac8e250..604bea2485ce65 100644 --- a/integration-tests/kubernetes/quarkus-standard-way/src/test/java/io/quarkus/it/kubernetes/KubernetesWithFlywayInitTest.java +++ b/integration-tests/kubernetes/quarkus-standard-way/src/test/java/io/quarkus/it/kubernetes/KubernetesWithFlywayInitTest.java @@ -68,6 +68,7 @@ public void assertGeneratedResources() throws IOException { assertThat(t.getSpec()).satisfies(podSpec -> { assertThat(podSpec.getInitContainers()).singleElement().satisfies(container -> { assertThat(container.getName()).isEqualTo("init"); + assertThat(container.getImage()).isEqualTo("groundnuty/k8s-wait-for:1.3"); }); });