From b1bd0a488009549fbe354445f4329ca422c8fdb0 Mon Sep 17 00:00:00 2001 From: Ioannis Canellos Date: Thu, 14 Nov 2019 16:21:41 +0200 Subject: [PATCH] feat (#5493): Add support for building and deploying container to Kubernetes. --- .../quarkus/deployment/QuarkusAugmentor.java | 6 +- .../deployment/pkg/ContainerConfig.java | 21 +++ .../builditem/DeploymentResultBuildItem.java | 26 ++++ .../deployment/pkg/builditem/DockerBuild.java | 21 +++ .../pkg/builditem/DockerImageBuildItem.java | 17 +++ .../builditem/DockerImageResultBuildItem.java | 30 +++++ .../deployment/pkg/steps/DockerBuildStep.java | 53 ++++++++ .../io/quarkus/deployment/util/ExecUtil.java | 62 +++++++++ extensions/container/deployment/pom.xml | 38 ++++++ .../container/deployment/DockerBuild.java | 28 ++++ .../container/deployment/DockerBuildStep.java | 123 ++++++++++++++++++ .../src/main/resources/Dockerfile.jvm | 28 ++++ .../src/main/resources/Dockerfile.native | 24 ++++ extensions/container/pom.xml | 21 +++ extensions/container/runtime/pom.xml | 38 ++++++ .../resources/META-INF/quarkus-extension.yaml | 9 ++ extensions/container/spi/pom.xml | 21 +++ .../deployment/KubernetesConfig.java | 24 ++++ .../deployment/KubernetesDeploy.java | 34 +++++ .../deployment/KubernetesDeployer.java | 44 +++++++ .../deployment/KubernetesProcessor.java | 12 +- 21 files changed, 678 insertions(+), 2 deletions(-) create mode 100644 core/deployment/src/main/java/io/quarkus/deployment/pkg/ContainerConfig.java create mode 100644 core/deployment/src/main/java/io/quarkus/deployment/pkg/builditem/DeploymentResultBuildItem.java create mode 100644 core/deployment/src/main/java/io/quarkus/deployment/pkg/builditem/DockerBuild.java create mode 100644 core/deployment/src/main/java/io/quarkus/deployment/pkg/builditem/DockerImageBuildItem.java create mode 100644 core/deployment/src/main/java/io/quarkus/deployment/pkg/builditem/DockerImageResultBuildItem.java create mode 100644 core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/DockerBuildStep.java create mode 100644 core/deployment/src/main/java/io/quarkus/deployment/util/ExecUtil.java create mode 100644 extensions/container/deployment/pom.xml create mode 100644 extensions/container/deployment/src/main/java/io/quarkus/container/deployment/DockerBuild.java create mode 100644 extensions/container/deployment/src/main/java/io/quarkus/container/deployment/DockerBuildStep.java create mode 100644 extensions/container/deployment/src/main/resources/Dockerfile.jvm create mode 100644 extensions/container/deployment/src/main/resources/Dockerfile.native create mode 100644 extensions/container/pom.xml create mode 100644 extensions/container/runtime/pom.xml create mode 100644 extensions/container/runtime/src/main/resources/META-INF/quarkus-extension.yaml create mode 100644 extensions/container/spi/pom.xml create mode 100644 extensions/kubernetes/deployment/src/main/java/io/quarkus/kubernetes/deployment/KubernetesConfig.java create mode 100644 extensions/kubernetes/deployment/src/main/java/io/quarkus/kubernetes/deployment/KubernetesDeploy.java create mode 100644 extensions/kubernetes/deployment/src/main/java/io/quarkus/kubernetes/deployment/KubernetesDeployer.java diff --git a/core/deployment/src/main/java/io/quarkus/deployment/QuarkusAugmentor.java b/core/deployment/src/main/java/io/quarkus/deployment/QuarkusAugmentor.java index d4c38d4dd..a80b99576 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/QuarkusAugmentor.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/QuarkusAugmentor.java @@ -33,7 +33,9 @@ import io.quarkus.deployment.builditem.LaunchModeBuildItem; import io.quarkus.deployment.builditem.LiveReloadBuildItem; import io.quarkus.deployment.builditem.ShutdownContextBuildItem; import io.quarkus.deployment.pkg.builditem.BuildSystemTargetBuildItem; +import io.quarkus.deployment.pkg.builditem.ContainerImageResultBuildItem; import io.quarkus.deployment.pkg.builditem.CurateOutcomeBuildItem; +import io.quarkus.deployment.pkg.builditem.DeploymentResultBuildItem; import io.quarkus.runtime.LaunchMode; public class QuarkusAugmentor { @@ -107,7 +109,9 @@ public class QuarkusAugmentor { chainBuilder.addFinal(i); } chainBuilder.addFinal(GeneratedClassBuildItem.class) - .addFinal(GeneratedResourceBuildItem.class); + .addFinal(GeneratedResourceBuildItem.class) + .addFinal(ContainerImageResultBuildItem.class) + .addFinal(DeploymentResultBuildItem.class); for (Consumer i : buildChainCustomizers) { i.accept(chainBuilder); diff --git a/core/deployment/src/main/java/io/quarkus/deployment/pkg/ContainerConfig.java b/core/deployment/src/main/java/io/quarkus/deployment/pkg/ContainerConfig.java new file mode 100644 index 000000000..7c10b16cb --- /dev/null +++ b/core/deployment/src/main/java/io/quarkus/deployment/pkg/ContainerConfig.java @@ -0,0 +1,21 @@ + +package io.quarkus.deployment.pkg; + +import io.quarkus.runtime.annotations.ConfigItem; +import io.quarkus.runtime.annotations.ConfigRoot; + +@ConfigRoot +public class ContainerConfig { + + /** + * Flag that specifies if container build is enabled + */ + @ConfigItem + public boolean build; + + /** + * Flag that specifies if container deploy is enabled + */ + @ConfigItem + public boolean deploy; +} diff --git a/core/deployment/src/main/java/io/quarkus/deployment/pkg/builditem/DeploymentResultBuildItem.java b/core/deployment/src/main/java/io/quarkus/deployment/pkg/builditem/DeploymentResultBuildItem.java new file mode 100644 index 000000000..1e54d57c0 --- /dev/null +++ b/core/deployment/src/main/java/io/quarkus/deployment/pkg/builditem/DeploymentResultBuildItem.java @@ -0,0 +1,26 @@ + +package io.quarkus.deployment.pkg.builditem; + +import java.util.Map; + +import io.quarkus.builder.item.SimpleBuildItem; + +public final class DeploymentResultBuildItem extends SimpleBuildItem { + + private final String name; + private final Map labels; + + public DeploymentResultBuildItem(String name, Map labels) { + this.name = name; + this.labels = labels; + } + + public String getName() { + return this.name; + } + + public Map getLabels() { + return this.labels; + } + +} diff --git a/core/deployment/src/main/java/io/quarkus/deployment/pkg/builditem/DockerBuild.java b/core/deployment/src/main/java/io/quarkus/deployment/pkg/builditem/DockerBuild.java new file mode 100644 index 000000000..d2d156c47 --- /dev/null +++ b/core/deployment/src/main/java/io/quarkus/deployment/pkg/builditem/DockerBuild.java @@ -0,0 +1,21 @@ + +package io.quarkus.deployment.pkg.builditem; + +import java.util.function.BooleanSupplier; + +import io.quarkus.deployment.pkg.ContainerConfig; +import io.quarkus.deployment.util.ExecUtil; + +public class DockerBuild implements BooleanSupplier { + + private final ContainerConfig containerConfig; + + DockerBuild(ContainerConfig containerConfig) { + this.containerConfig = containerConfig; + } + + @Override + public boolean getAsBoolean() { + return containerConfig.build && ExecUtil.exec("docker", "version"); + } +} diff --git a/core/deployment/src/main/java/io/quarkus/deployment/pkg/builditem/DockerImageBuildItem.java b/core/deployment/src/main/java/io/quarkus/deployment/pkg/builditem/DockerImageBuildItem.java new file mode 100644 index 000000000..ea447b22f --- /dev/null +++ b/core/deployment/src/main/java/io/quarkus/deployment/pkg/builditem/DockerImageBuildItem.java @@ -0,0 +1,17 @@ + +package io.quarkus.deployment.pkg.builditem; + +import io.quarkus.builder.item.SimpleBuildItem; + +public final class DockerImageBuildItem extends SimpleBuildItem { + + private final String image; + + public DockerImageBuildItem(String image) { + this.image = image; + } + + public String getImage() { + return this.image; + } +} diff --git a/core/deployment/src/main/java/io/quarkus/deployment/pkg/builditem/DockerImageResultBuildItem.java b/core/deployment/src/main/java/io/quarkus/deployment/pkg/builditem/DockerImageResultBuildItem.java new file mode 100644 index 000000000..555649add --- /dev/null +++ b/core/deployment/src/main/java/io/quarkus/deployment/pkg/builditem/DockerImageResultBuildItem.java @@ -0,0 +1,30 @@ + +package io.quarkus.deployment.pkg.builditem; + +import io.quarkus.builder.item.SimpleBuildItem; + +public final class DockerImageResultBuildItem extends SimpleBuildItem { + + private final String imageId; + private final String repository; + private final String tag; + + public DockerImageResultBuildItem(String imageId, String repository, String tag) { + this.imageId = imageId; + this.repository = repository; + this.tag = tag; + } + + public String getImageId() { + return this.imageId; + } + + public String getRepository() { + return this.repository; + } + + public String getTag() { + return this.tag; + } + +} diff --git a/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/DockerBuildStep.java b/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/DockerBuildStep.java new file mode 100644 index 000000000..f57244b9b --- /dev/null +++ b/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/DockerBuildStep.java @@ -0,0 +1,53 @@ + +package io.quarkus.deployment.pkg.steps; + +import java.util.Optional; + +import org.jboss.logging.Logger; + +import io.quarkus.deployment.annotations.BuildStep; +import io.quarkus.deployment.builditem.ApplicationInfoBuildItem; +import io.quarkus.deployment.pkg.builditem.DockerBuild; +import io.quarkus.deployment.pkg.builditem.DockerImageBuildItem; +import io.quarkus.deployment.pkg.builditem.DockerImageResultBuildItem; +import io.quarkus.deployment.pkg.builditem.JarBuildItem; +import io.quarkus.deployment.pkg.builditem.ModuleDirBuildItem; +import io.quarkus.deployment.pkg.builditem.NativeImageBuildItem; +import io.quarkus.deployment.util.ExecUtil; + +public class DockerBuildStep { + + private static final Logger log = Logger.getLogger(DockerBuildStep.class); + + @BuildStep(onlyIf = DockerBuild.class, onlyIfNot = NativeBuild.class) + public DockerImageResultBuildItem dockerBuildFromJar(ApplicationInfoBuildItem app, ModuleDirBuildItem moduledir, + Optional dockerImage, + JarBuildItem artifact) { + log.info("Building docker image for jar."); + ExecUtil.exec(moduledir.getPath().toFile(), + "docker", "build", + "-f", + moduledir.getPath().resolve("src").resolve("main").resolve("docker").resolve("Dockerfile.jvm").toAbsolutePath() + .toString(), + "-t", dockerImage.map(d -> d.getImage()).orElse(app.getName() + ":" + app.getVersion()), + moduledir.getPath().toAbsolutePath().toString()); + + return new DockerImageResultBuildItem(null, null, null); + } + + @BuildStep(onlyIf = { DockerBuild.class, NativeBuild.class }) + public DockerImageResultBuildItem dockerBuildFromNaticeImage(ApplicationInfoBuildItem app, ModuleDirBuildItem moduledir, + Optional dockerImage, + NativeImageBuildItem nativeImage) { + log.info("Building docker image for native image."); + ExecUtil.exec(moduledir.getPath().toFile(), + "docker", "build", + "-f", + moduledir.getPath().resolve("src").resolve("main").resolve("docker").resolve("Dockerfile.native") + .toAbsolutePath() + .toString(), + "-t", dockerImage.map(d -> d.getImage()).orElse(app.getName() + ":" + app.getVersion() + "-native"), + moduledir.getPath().toAbsolutePath().toString()); + return new DockerImageResultBuildItem(null, null, null); + } +} diff --git a/core/deployment/src/main/java/io/quarkus/deployment/util/ExecUtil.java b/core/deployment/src/main/java/io/quarkus/deployment/util/ExecUtil.java new file mode 100644 index 000000000..bffa29b11 --- /dev/null +++ b/core/deployment/src/main/java/io/quarkus/deployment/util/ExecUtil.java @@ -0,0 +1,62 @@ + +package io.quarkus.deployment.util; + +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.InputStreamReader; + +public class ExecUtil { + + /** + * Execute the specified command from within the current directory. + * + * @param command The command + * @param args The command arguments + * @return true if commands where executed successfully + */ + public static boolean exec(String command, String... args) { + return exec(new File("."), command, args); + } + + /** + * Execute the specified command from within the specified directory. + * + * @param directory The directory + * @param command The command + * @param args The command arguments + * @return true if commands where executed successfully + */ + public static boolean exec(File directory, String command, String... args) { + Process process = null; + try { + String[] cmd = new String[args.length + 1]; + cmd[0] = command; + if (args.length > 0) { + System.arraycopy(args, 0, cmd, 1, args.length); + } + process = new ProcessBuilder() + .directory(directory) + .command(cmd) + .redirectErrorStream(true) + .start(); + + try (InputStreamReader isr = new InputStreamReader(process.getInputStream()); + BufferedReader reader = new BufferedReader(isr)) { + + for (String line = reader.readLine(); line != null; line = reader.readLine()) { + System.out.println(line); + } + process.waitFor(); + } + } catch (IOException e) { + return false; + } catch (InterruptedException e) { + return false; + } + if (process != null) { + return process.exitValue() == 0; + } + return false; + } +} diff --git a/extensions/container/deployment/pom.xml b/extensions/container/deployment/pom.xml new file mode 100644 index 000000000..d09027f6d --- /dev/null +++ b/extensions/container/deployment/pom.xml @@ -0,0 +1,38 @@ + + + + quarkus-container-parent + io.quarkus + 999-SNAPSHOT + + 4.0.0 + + quarkus-container-deployment + Quarkus - Container - Deployment + + + + io.quarkus + quarkus-core-deployment + + + + + + + maven-compiler-plugin + + + + io.quarkus + quarkus-extension-processor + ${project.version} + + + + + + + diff --git a/extensions/container/deployment/src/main/java/io/quarkus/container/deployment/DockerBuild.java b/extensions/container/deployment/src/main/java/io/quarkus/container/deployment/DockerBuild.java new file mode 100644 index 000000000..1b0d5acc8 --- /dev/null +++ b/extensions/container/deployment/src/main/java/io/quarkus/container/deployment/DockerBuild.java @@ -0,0 +1,28 @@ + +package io.quarkus.container.deployment; + +import java.util.function.BooleanSupplier; + +import io.quarkus.deployment.pkg.ContainerConfig; +import io.quarkus.deployment.util.ExecUtil; + +public class DockerBuild implements BooleanSupplier { + + private final ContainerConfig containerConfig; + + DockerBuild(ContainerConfig containerConfig) { + this.containerConfig = containerConfig; + } + + @Override + public boolean getAsBoolean() { + if (containerConfig.build) { + try { + return ExecUtil.exec("docker", "version"); + } catch (Exception e) { + return false; + } + } + return false; + } +} diff --git a/extensions/container/deployment/src/main/java/io/quarkus/container/deployment/DockerBuildStep.java b/extensions/container/deployment/src/main/java/io/quarkus/container/deployment/DockerBuildStep.java new file mode 100644 index 000000000..d50eea474 --- /dev/null +++ b/extensions/container/deployment/src/main/java/io/quarkus/container/deployment/DockerBuildStep.java @@ -0,0 +1,123 @@ + +package io.quarkus.container.deployment; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; +import java.util.Optional; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Function; + +import org.jboss.logging.Logger; + +import io.quarkus.deployment.annotations.BuildStep; +import io.quarkus.deployment.builditem.ApplicationInfoBuildItem; +import io.quarkus.deployment.pkg.builditem.ContainerImageBuildItem; +import io.quarkus.deployment.pkg.builditem.ContainerImageResultBuildItem; +import io.quarkus.deployment.pkg.builditem.JarBuildItem; +import io.quarkus.deployment.pkg.builditem.NativeImageBuildItem; +import io.quarkus.deployment.pkg.builditem.OutputTargetBuildItem; +import io.quarkus.deployment.pkg.steps.NativeBuild; +import io.quarkus.deployment.util.ExecUtil; +import io.quarkus.deployment.util.ImageUtil; + +public class DockerBuildStep { + + private static final Logger log = Logger.getLogger(DockerBuildStep.class); + private static final String DOCKERFILE_JVM = "Dockerfile.jvm"; + private static final String DOCKERFILE_NATIVE = "Dockerfile.native"; + + @BuildStep(onlyIf = DockerBuild.class, onlyIfNot = NativeBuild.class) + public ContainerImageResultBuildItem dockerBuildFromJar(ApplicationInfoBuildItem app, + OutputTargetBuildItem out, + Optional dockerImage, + JarBuildItem artifact) { + log.info("Building docker image for jar."); + String image = dockerImage.map(d -> d.getImage()).orElse(app.getName() + ":" + app.getVersion()); + ImageIdReader reader = new ImageIdReader(); + Path dockerFile = extractDockerfile(DOCKERFILE_JVM); + ExecUtil.exec(out.getOutputDirectory().toFile(), + reader, + "docker", "build", + "-f", + dockerFile.resolve(DOCKERFILE_JVM).toAbsolutePath().toString(), + "-t", image, + out.getOutputDirectory().toAbsolutePath().toString()); + + return new ContainerImageResultBuildItem(reader.getImageId(), ImageUtil.getRepository(image), ImageUtil.getTag(image)); + } + + @BuildStep(onlyIf = { DockerBuild.class, NativeBuild.class }) + public ContainerImageResultBuildItem dockerBuildFromNativeImage(ApplicationInfoBuildItem app, + OutputTargetBuildItem out, + Optional dockerImage, + NativeImageBuildItem nativeImage) { + log.info("Building docker image for native image."); + String image = dockerImage.map(d -> d.getImage()).orElse(app.getName() + ":" + app.getVersion() + "-native"); + Path dockerFile = extractDockerfile(DOCKERFILE_NATIVE); + ImageIdReader reader = new ImageIdReader(); + ExecUtil.exec(out.getOutputDirectory().toFile(), + reader, + "docker", "build", + "-f", + dockerFile.resolve(DOCKERFILE_NATIVE).toAbsolutePath().toString(), + "-t", image, + out.getOutputDirectory().toAbsolutePath().toString()); + return new ContainerImageResultBuildItem(reader.getImageId(), ImageUtil.getRepository(image), ImageUtil.getTag(image)); + } + + private Path extractDockerfile(String resource) { + final Path path; + try { + path = Files.createTempDirectory("quarkus-docker"); + } catch (IOException e) { + throw new RuntimeException("Unable to setup environment for generating docker resources", e); + } + + try (InputStream jvm = getClass().getClassLoader().getResource(resource).openStream()) { + Files.copy(jvm, path.resolve(resource), StandardCopyOption.REPLACE_EXISTING); + } catch (IOException e) { + throw new RuntimeException("Unable to extract docker resource: " + resource, e); + } + return path; + } + + /** + * A function that creates a command output reader, that reads and holds the image id from the docker build output. + */ + private class ImageIdReader implements Function { + + private final AtomicReference id = new AtomicReference<>(); + + public String getImageId() { + return id.get(); + } + + @Override + public Runnable apply(InputStream t) { + return new Runnable() { + @Override + public void run() { + try (InputStreamReader isr = new InputStreamReader(t); + BufferedReader reader = new BufferedReader(isr)) { + + for (String line = reader.readLine(); line != null; line = reader.readLine()) { + if (line.startsWith("Succesfully built")) { + String[] parts = line.split(" "); + if (parts.length == 3) + id.set(parts[2]); + } + log.info(line); + } + } catch (IOException e) { + throw new RuntimeException(e); + } + } + }; + } + } +} diff --git a/extensions/container/deployment/src/main/resources/Dockerfile.jvm b/extensions/container/deployment/src/main/resources/Dockerfile.jvm new file mode 100644 index 000000000..dca4c07ed --- /dev/null +++ b/extensions/container/deployment/src/main/resources/Dockerfile.jvm @@ -0,0 +1,28 @@ +#### +# This Dockerfile is used in order to build a container that runs the Quarkus application in JVM mode +# +# Before building the docker image run: +# +# mvn package +# +# Then, build the image with: +# +# docker build -f src/main/docker/Dockerfile.jvm -t quarkus/stm-quickstart-jvm . +# +# Then run the container using: +# +# docker run -i --rm -p 8080:8080 quarkus/stm-quickstart-jvm +# +# Test the endpoint: +# +# curl -XPOST http://localhost:8080/stm +# java -jar ../stress/target/codeone-stress-1.0.jar requests=100 parallelism=50 url=/stm +# curl -XGET http://localhost:8080/stm +# +### +FROM fabric8/java-alpine-openjdk8-jre +ENV JAVA_OPTIONS="-Dquarkus.http.host=0.0.0.0 -Djava.util.logging.manager=org.jboss.logmanager.LogManager" +ENV AB_ENABLED=jmx_exporter +COPY lib/* /deployments/lib/ +COPY *-runner.jar /deployments/app.jar +ENTRYPOINT [ "/deployments/run-java.sh" ] diff --git a/extensions/container/deployment/src/main/resources/Dockerfile.native b/extensions/container/deployment/src/main/resources/Dockerfile.native new file mode 100644 index 000000000..732761664 --- /dev/null +++ b/extensions/container/deployment/src/main/resources/Dockerfile.native @@ -0,0 +1,24 @@ +#### +# This Dockerfile is used in order to build a container that runs the Quarkus application in native (no JVM) mode +# +# Before building the docker image run: +# +# mvn package -Pnative -Dquarkus.native.container-build=true +# +# Then, build the image with: +# +# docker build -f src/main/docker/Dockerfile.native -t quarkus/hello-cloud . +# +# Then run the container using: +# +# docker run -i --rm -p 8080:8080 quarkus/hello-cloud +# +### +FROM registry.access.redhat.com/ubi8/ubi-minimal +WORKDIR /work/ +COPY *-runner /work/application +RUN chmod 775 /work +EXPOSE 8080 +CMD ["./application", "-Dquarkus.http.host=0.0.0.0"] +~ +~ diff --git a/extensions/container/pom.xml b/extensions/container/pom.xml new file mode 100644 index 000000000..51fbb8ae6 --- /dev/null +++ b/extensions/container/pom.xml @@ -0,0 +1,21 @@ + + + + quarkus-build-parent + io.quarkus + 999-SNAPSHOT + ../../build-parent/pom.xml + + 4.0.0 + + quarkus-container-parent + Quarkus - Container + pom + + deployment + runtime + spi + + diff --git a/extensions/container/runtime/pom.xml b/extensions/container/runtime/pom.xml new file mode 100644 index 000000000..a234d47cc --- /dev/null +++ b/extensions/container/runtime/pom.xml @@ -0,0 +1,38 @@ + + + 4.0.0 + + + io.quarkus + quarkus-container-parent + 999-SNAPSHOT + + + quarkus-container + Quarkus - Container - Runtime + Manage container builds + + + + io.quarkus + quarkus-bootstrap-maven-plugin + + + maven-compiler-plugin + + + + io.quarkus + quarkus-extension-processor + ${project.version} + + + + + + + + + diff --git a/extensions/container/runtime/src/main/resources/META-INF/quarkus-extension.yaml b/extensions/container/runtime/src/main/resources/META-INF/quarkus-extension.yaml new file mode 100644 index 000000000..e159d75dd --- /dev/null +++ b/extensions/container/runtime/src/main/resources/META-INF/quarkus-extension.yaml @@ -0,0 +1,9 @@ +--- +name: "Docker" +metadata: + keywords: + - "docker" + guide: "https://quarkus.io/guides/docker" + categories: + - "cloud" + status: "stable" diff --git a/extensions/container/spi/pom.xml b/extensions/container/spi/pom.xml new file mode 100644 index 000000000..0ccac1010 --- /dev/null +++ b/extensions/container/spi/pom.xml @@ -0,0 +1,21 @@ + + + quarkus-container-parent + io.quarkus + 999-SNAPSHOT + + 4.0.0 + + quarkus-container-spi + Quarkus - Container - SPI + Extensions that provide Container features should include this module and the corresponding BuildItems + + + + io.quarkus + quarkus-core-deployment + + + diff --git a/extensions/kubernetes/deployment/src/main/java/io/quarkus/kubernetes/deployment/KubernetesConfig.java b/extensions/kubernetes/deployment/src/main/java/io/quarkus/kubernetes/deployment/KubernetesConfig.java new file mode 100644 index 000000000..fd891f268 --- /dev/null +++ b/extensions/kubernetes/deployment/src/main/java/io/quarkus/kubernetes/deployment/KubernetesConfig.java @@ -0,0 +1,24 @@ + +package io.quarkus.kubernetes.deployment; + +import io.quarkus.runtime.annotations.ConfigItem; +import io.quarkus.runtime.annotations.ConfigRoot; + +@ConfigRoot +public class KubernetesConfig { + + private static final String KUBERNETES = "kubernetes"; + private static final String OPENSHIFT = "openshift"; + private static final String KNATIVE = "knative"; + + /** + * The target deployment platform. + * Defaults to kubernetes. Can be kubernetes, openshift, knative or any combination of the above as coma separated list. + */ + @ConfigItem(defaultValue = KUBERNETES) + String deploymentTarget = KUBERNETES; + + public String getDeploymentTarget() { + return this.deploymentTarget; + } +} diff --git a/extensions/kubernetes/deployment/src/main/java/io/quarkus/kubernetes/deployment/KubernetesDeploy.java b/extensions/kubernetes/deployment/src/main/java/io/quarkus/kubernetes/deployment/KubernetesDeploy.java new file mode 100644 index 000000000..b5bc53462 --- /dev/null +++ b/extensions/kubernetes/deployment/src/main/java/io/quarkus/kubernetes/deployment/KubernetesDeploy.java @@ -0,0 +1,34 @@ + +package io.quarkus.kubernetes.deployment; + +import java.util.function.BooleanSupplier; + +import io.quarkus.container.deployment.ContainerConfig; +import io.quarkus.container.deployment.DockerBuild.OutputFilter; +import io.quarkus.deployment.util.ExecUtil; + +public class KubernetesDeploy implements BooleanSupplier { + + private KubernetesConfig kubernetesConfig; + private ContainerConfig containerConfig; + + KubernetesDeploy(ContainerConfig containerConfig, KubernetesConfig kubernetesConfig) { + this.containerConfig = containerConfig; + this.kubernetesConfig = kubernetesConfig; + } + + @Override + public boolean getAsBoolean() { + if (containerConfig.deploy) { + try { + if (kubernetesConfig.getDeploymentTarget().contains(DeploymentTarget.OPENSHIFT)) { + return ExecUtil.exec("oc", "version"); + } + return ExecUtil.exec("kubectl", "version"); + } catch (Exception e) { + return false; + } + } + return false; + } +} diff --git a/extensions/kubernetes/deployment/src/main/java/io/quarkus/kubernetes/deployment/KubernetesDeployer.java b/extensions/kubernetes/deployment/src/main/java/io/quarkus/kubernetes/deployment/KubernetesDeployer.java new file mode 100644 index 000000000..ab7378e8f --- /dev/null +++ b/extensions/kubernetes/deployment/src/main/java/io/quarkus/kubernetes/deployment/KubernetesDeployer.java @@ -0,0 +1,44 @@ + +package io.quarkus.kubernetes.deployment; + +import org.jboss.logging.Logger; + +import io.dekorate.deps.kubernetes.api.model.HasMetadata; +import io.dekorate.deps.kubernetes.api.model.KubernetesList; +import io.dekorate.deps.kubernetes.client.KubernetesClient; +import io.dekorate.utils.Clients; +import io.dekorate.utils.Serialization; +import io.quarkus.container.spi.ContainerImageResultBuildItem; +import io.quarkus.deployment.IsNormal; +import io.quarkus.deployment.annotations.BuildProducer; +import io.quarkus.deployment.annotations.BuildStep; +import io.quarkus.deployment.builditem.ApplicationInfoBuildItem; +import io.quarkus.deployment.pkg.builditem.DeploymentResultBuildItem; +import io.quarkus.deployment.pkg.builditem.DockerImageResultBuildItem; +import io.quarkus.deployment.pkg.builditem.OutputTargetBuildItem; +import io.quarkus.deployment.util.ExecUtil; + +public class KubernetesDeployer { + + private static final Logger LOG = Logger.getLogger(KubernetesDeployer.class); + + @BuildStep(onlyIf = { IsNormal.class, KubernetesDeploy.class }) + public void deploy(KubernetesClientBuildItem kubernetesClient, + ApplicationInfoBuildItem applicationInfo, + Optional containerImage, + OutputTargetBuildItem outputTarget, + BuildProducer deploymentResult) { + + kubernetesConfig.getDeploymentTarget().stream().map(Enum::name).map(String::toLowerCase).findFirst().ifPresent(d -> { + LOG.info("Deploying to " + d + "."); + ExecUtil.exec(outputTarget.getOutputDirectory().toFile(), "oc", "apply", "-f", + "kubernetes/" + d + ".yml"); + }); + + return new DeploymentResultBuildItem(null, null); + } + + public void setKubernetesconfig(KubernetesConfig kubernetesConfig) { + this.kubernetesConfig = kubernetesConfig; + } +} diff --git a/extensions/kubernetes/deployment/src/main/java/io/quarkus/kubernetes/deployment/KubernetesProcessor.java b/extensions/kubernetes/deployment/src/main/java/io/quarkus/kubernetes/deployment/KubernetesProcessor.java index 2ddf821f6..1b747cf20 100644 --- a/extensions/kubernetes/deployment/src/main/java/io/quarkus/kubernetes/deployment/KubernetesProcessor.java +++ b/extensions/kubernetes/deployment/src/main/java/io/quarkus/kubernetes/deployment/KubernetesProcessor.java @@ -22,6 +22,7 @@ import org.eclipse.microprofile.config.ConfigProvider; import io.dekorate.Session; import io.dekorate.SessionWriter; +import io.dekorate.kubernetes.config.ImageConfiguration; import io.dekorate.kubernetes.config.PortBuilder; import io.dekorate.kubernetes.config.ProbeBuilder; import io.dekorate.kubernetes.configurator.AddPort; @@ -29,11 +30,13 @@ import io.dekorate.kubernetes.decorator.AddLivenessProbeDecorator; import io.dekorate.kubernetes.decorator.AddReadinessProbeDecorator; import io.dekorate.kubernetes.decorator.AddRoleBindingResourceDecorator; import io.dekorate.kubernetes.decorator.AddServiceAccountResourceDecorator; +import io.dekorate.kubernetes.decorator.ApplyImageDecorator; import io.dekorate.kubernetes.decorator.ApplyServiceAccountNamedDecorator; import io.dekorate.processor.SimpleFileWriter; import io.dekorate.project.BuildInfo; import io.dekorate.project.FileProjectFactory; import io.dekorate.project.Project; +import io.dekorate.utils.Images; import io.dekorate.utils.Maps; import io.dekorate.utils.Strings; import io.quarkus.deployment.IsNormal; @@ -44,6 +47,7 @@ import io.quarkus.deployment.builditem.ArchiveRootBuildItem; import io.quarkus.deployment.builditem.FeatureBuildItem; import io.quarkus.deployment.builditem.GeneratedFileSystemResourceBuildItem; import io.quarkus.deployment.pkg.PackageConfig; +import io.quarkus.deployment.pkg.builditem.ContainerImageBuildItem; import io.quarkus.deployment.pkg.builditem.OutputTargetBuildItem; import io.quarkus.kubernetes.spi.KubernetesHealthLivenessPathBuildItem; import io.quarkus.kubernetes.spi.KubernetesHealthReadinessPathBuildItem; @@ -77,6 +81,7 @@ class KubernetesProcessor { PackageConfig packageConfig, List kubernetesRoleBuildItems, List kubernetesPortBuildItems, + Optional containerImageBuildItem, Optional kubernetesHealthLivenessPathBuildItem, Optional kubernetesHealthReadinessPathBuildItem) throws UnsupportedEncodingException { @@ -136,8 +141,10 @@ class KubernetesProcessor { session.setWriter(sessionWriter); session.feed(Maps.fromProperties(configAsMap)); + //apply build item configurations to the dekorate session. applyBuildItems(session, applicationInfo, kubernetesRoleBuildItems, kubernetesPortBuildItems, + containerImageBuildItem, kubernetesHealthLivenessPathBuildItem, kubernetesHealthReadinessPathBuildItem); @@ -181,9 +188,12 @@ class KubernetesProcessor { private void applyBuildItems(Session session, ApplicationInfoBuildItem applicationInfo, List kubernetesRoleBuildItems, List kubernetesPortBuildItems, + Optional containerImageResultItem, Optional kubernetesHealthLivenessPathBuildItem, Optional kubernetesHealthReadinessPathBuildItem) { + containerImageResultItem.ifPresent(c -> session.resources() + .decorate(new ApplyImageDecorator(applicationInfo.getName(), c.getImage()))); //Handle ports final Map ports = verifyPorts(kubernetesPortBuildItems); ports.entrySet().stream() @@ -245,7 +255,7 @@ class KubernetesProcessor { /** * Returns the name of the generators that can handle the specified key. - * + * * @param key The key. * @return The generator name or null if the key format is unexpected. */