Skip to content

Commit

Permalink
Allow overriding generated init-container of extensions from user config
Browse files Browse the repository at this point in the history
  • Loading branch information
Sgitario committed May 4, 2023
1 parent 29a134f commit 66a2834
Show file tree
Hide file tree
Showing 12 changed files with 252 additions and 16 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -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<PodSpecBuilder> {

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());
}
}
Original file line number Diff line number Diff line change
@@ -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<? extends Decorator>[] before() {
return new Class[] { AddInitContainerFromUserConfigDecorator.class };
}
}
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand Down
Original file line number Diff line number Diff line change
@@ -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;

Expand All @@ -20,22 +20,26 @@

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<InitTaskBuildItem> initTasks,
ContainerImageInfoBuildItem image,
List<InitTaskBuildItem> initTasks,
BuildProducer<KubernetesJobBuildItem> jobs,
BuildProducer<KubernetesInitContainerBuildItem> initContainers,
BuildProducer<KubernetesEnvBuildItem> env,
BuildProducer<KubernetesRoleBuildItem> roles,
BuildProducer<KubernetesRoleBindingBuildItem> roleBindings,
BuildProducer<DecoratorBuildItem> decorators) {

initTasks.forEach(task -> {
initContainers.produce(KubernetesInitContainerBuildItem.create("groundnuty/k8s-wait-for:1.3")
.withTarget(target)
.withArguments(Arrays.asList("job", task.getName())));
List<String> 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)
Expand Down Expand Up @@ -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));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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())
Expand Down Expand Up @@ -750,7 +749,8 @@ private static List<DecoratorBuildItem> createPodDecorators(Optional<Project> 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 -> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
@@ -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<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()).singleElement().satisfies(container -> {
assertThat(container.getName()).isEqualTo("init");
assertThat(container.getImage()).isEqualTo(OVERRIDDEN_WAITER_IMAGE);
});

});
});
});
});

Optional<Job> 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> roleBinding = kubernetesList.stream().filter(
r -> r instanceof RoleBinding && (NAME + "-view-jobs").equals(r.getMetadata().getName()))
.map(r -> (RoleBinding) r).findFirst();
assertTrue(roleBinding.isPresent());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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");
});

});
Expand Down

0 comments on commit 66a2834

Please sign in to comment.