Skip to content

Commit

Permalink
Add init-tasks property to allow the init task configuration by tas…
Browse files Browse the repository at this point in the history
…k name

Fix quarkusio#33097
  • Loading branch information
Sgitario authored and michelle-purcell committed May 29, 2023
1 parent cb12422 commit 2ff7365
Show file tree
Hide file tree
Showing 7 changed files with 172 additions and 35 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package io.quarkus.kubernetes.deployment;

import io.quarkus.runtime.annotations.ConfigGroup;
import io.quarkus.runtime.annotations.ConfigItem;

@ConfigGroup
public class InitTaskConfig {
/**
* If true, the init task will be generated. Otherwise, the init task resource generation will be skipped.
*/
@ConfigItem(defaultValue = "true")
public boolean enabled;

/**
* The init task image to use by the init-container.
*/
@ConfigItem(defaultValue = "groundnuty/k8s-wait-for:no-root-v1.7")
public String image;
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package io.quarkus.kubernetes.deployment;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;

import io.dekorate.kubernetes.config.EnvBuilder;
import io.dekorate.kubernetes.decorator.AddEnvVarDecorator;
Expand All @@ -26,51 +26,53 @@ public class InitTaskProcessor {
static void process(
String target, // kubernetes, openshift, etc.
String name,
ContainerImageInfoBuildItem image, List<InitTaskBuildItem> initTasks,
ContainerImageInfoBuildItem image,
List<InitTaskBuildItem> initTasks,
Map<String, InitTaskConfig> initTasksConfig,
BuildProducer<KubernetesJobBuildItem> jobs,
BuildProducer<KubernetesInitContainerBuildItem> initContainers,
BuildProducer<KubernetesEnvBuildItem> env,
BuildProducer<KubernetesRoleBuildItem> roles,
BuildProducer<KubernetesRoleBindingBuildItem> roleBindings,
BuildProducer<DecoratorBuildItem> decorators) {

List<String> initContainerWaiterArgs = new ArrayList<>(initTasks.size() + 1);
initContainerWaiterArgs.add("job");
boolean generateRoleForJobs = false;
for (InitTaskBuildItem task : initTasks) {
InitTaskConfig config = initTasksConfig.get(task.getName());
if (config == null || config.enabled) {
generateRoleForJobs = true;
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())));

});
});
initContainers.produce(KubernetesInitContainerBuildItem.create(INIT_CONTAINER_WAITER_NAME,
config == null ? INIT_CONTAINER_WAITER_DEFAULT_IMAGE : config.image)
.withTarget(target)
.withArguments(List.of("job", task.getName())));
}
}

if (!initTasks.isEmpty()) {
if (generateRoleForJobs) {
roles.produce(new KubernetesRoleBuildItem("view-jobs", Collections.singletonList(
new PolicyRule(
Collections.singletonList("batch"),
Collections.singletonList("jobs"),
List.of("get"))),
target));
roleBindings.produce(new KubernetesRoleBindingBuildItem(null, "view-jobs", false, target));

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 @@ -340,11 +340,25 @@ 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.
*
* @Deprecated use {@link #initTasks} configuration instead
*/
@Deprecated(since = "3.1", forRemoval = true)
@ConfigItem(defaultValue = "true")
boolean externalizeInit;

/**
* Init tasks configuration.
*
* 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
Map<String, InitTaskConfig> initTasks;

/**
* Switch used to control whether non-idempotent fields are included in generated kubernetes resources to improve
* git-ops compatibility
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -568,11 +568,25 @@ public EnvVarsConfig getEnv() {
* 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.
*
* @Deprecated use {@link #initTasks} configuration instead
*/
@Deprecated(since = "3.1", forRemoval = true)
@ConfigItem(defaultValue = "true")
boolean externalizeInit;

/**
* Init tasks configuration.
*
* 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
Map<String, InitTaskConfig> initTasks;

/**
* Switch used to control whether non-idempotent fields are included in generated kubernetes resources to improve
* git-ops compatibility
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -384,8 +384,8 @@ void externalizeInitTasks(
BuildProducer<DecoratorBuildItem> 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.initTasks,
jobs, initContainers, env, roles, roleBindings, decorators);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -293,8 +293,8 @@ void externalizeInitTasks(
BuildProducer<DecoratorBuildItem> 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.initTasks,
jobs, initContainers, env, roles, roleBindings, decorators);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
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")
.overrideConfigKey("quarkus.kubernetes.init-tasks.\"" + NAME + "-flyway-init\".enabled", "false")
.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()).noneSatisfy(container -> {
assertThat(container.getName()).isEqualTo("init");
});
});
});
});
});

Optional<Job> 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> roleBinding = kubernetesList.stream().filter(
r -> r instanceof RoleBinding && (NAME + "-view-jobs").equals(r.getMetadata().getName()))
.map(r -> (RoleBinding) r).findFirst();
assertFalse(roleBinding.isPresent());
}
}

0 comments on commit 2ff7365

Please sign in to comment.