diff --git a/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/ApplyInitContainerDecorator.java b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/ApplyInitContainerDecorator.java new file mode 100644 index 00000000000000..d3ac3eb54bce27 --- /dev/null +++ b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/ApplyInitContainerDecorator.java @@ -0,0 +1,70 @@ +package io.quarkus.kubernetes.deployment; + +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 class ApplyInitContainerDecorator extends NamedResourceDecorator { + + private final Container container; + + public ApplyInitContainerDecorator(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::existsContainerByName)) { + 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::existsContainerByName); + + 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 existsContainerByName(ContainerBuilder containerBuilder) { + return containerBuilder.getName().equals(container.getName()); + } +} \ No newline at end of file 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..bc74c3d1c078b5 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 @@ -35,7 +35,7 @@ public Class[] after() { ApplyImagePullPolicyDecorator.class, ApplyWorkingDirDecorator.class, ApplyCommandDecorator.class, ApplyArgsDecorator.class, ApplyServiceAccountNamedDecorator.class, AddReadinessProbeDecorator.class, AddLivenessProbeDecorator.class, ApplyApplicationContainerDecorator.class, AddSidecarDecorator.class, - AddInitContainerDecorator.class }; + ApplyInitContainerDecorator.class }; } } 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..2b40dc9e360b0a 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 @@ -26,6 +26,7 @@ import io.dekorate.kubernetes.config.Annotation; import io.dekorate.kubernetes.config.ConfigMapVolumeBuilder; +import io.dekorate.kubernetes.config.Container; import io.dekorate.kubernetes.config.EnvBuilder; import io.dekorate.kubernetes.config.MountBuilder; import io.dekorate.kubernetes.config.Port; @@ -40,7 +41,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 +602,7 @@ public void andThenVisit(ContainerBuilder builder) { } result.add(new DecoratorBuildItem(target, - new AddInitContainerDecorator(name, containerBuilder + new ApplyInitContainerDecorator(name, containerBuilder .addAllToEnvVars(item.getEnvVars().entrySet().stream().map(e -> new EnvBuilder() .withName(e.getKey()) .withValue(e.getValue()) @@ -750,7 +750,8 @@ private static List createPodDecorators(Optional pr }); config.getInitContainers().entrySet().forEach(e -> { - result.add(new DecoratorBuildItem(target, new AddInitContainerDecorator(name, ContainerConverter.convert(e)))); + Container container = ContainerConverter.convert(e); + result.add(new DecoratorBuildItem(target, new ApplyInitContainerDecorator(name, container))); }); 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..f99f30c98bced9 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 ApplyInitContainerDecorator(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..77d5410d9984cd --- /dev/null +++ b/integration-tests/kubernetes/quarkus-standard-way/src/test/java/io/quarkus/it/kubernetes/KubernetesWithFlywayInitOverrideWaiterImageTest.java @@ -0,0 +1,114 @@ +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); + assertThat(container.getArgs()).containsExactly("job", NAME + "-flyway-init"); + }); + + }); + }); + }); + }); + + Optional job = kubernetesList.stream() + .filter(j -> "Job".equals(j.getKind()) && (NAME + "-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(NAME + "-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()); + } +}