From 5083cf2f75bb61ae0f9e89216b4cf731effadf61 Mon Sep 17 00:00:00 2001 From: Ioannis Canellos Date: Wed, 5 Feb 2020 13:45:08 +0200 Subject: [PATCH] refactor: Move KubernetesClientBuildConfig to an spi module so that it can be used by other extensions. --- bom/deployment/pom.xml | 7 +- .../quarkus/deployment/QuarkusAugmentor.java | 2 - .../deployment/pkg/ContainerConfig.java | 21 ------ extensions/container/deployment/pom.xml | 4 ++ .../container/deployment/ContainerConfig.java | 45 +++++++++++++ .../deployment/ContainerProcessor.java | 35 ++++++++++ .../container/deployment/DockerBuild.java | 50 +++++++++++++- .../container/deployment/DockerBuildStep.java | 33 ++++++--- .../container}/deployment/util/ImageUtil.java | 28 +++++++- extensions/container/pom.xml | 4 +- .../spi}/ContainerImageBuildItem.java | 2 +- .../spi}/ContainerImageResultBuildItem.java | 4 +- .../kubernetes-client/deployment/pom.xml | 4 ++ .../deployment/KubernetesClientBuildStep.java | 17 +++++ extensions/kubernetes-client/pom.xml | 1 + .../runtime/KubernetesClientBuildConfig.java | 47 ++++++------- .../runtime/KubernetesClientProducer.java | 29 +------- .../client/runtime/KubernetesClientUtils.java | 42 ++++++++++++ extensions/kubernetes-client/spi/pom.xml | 57 ++++++++++++++++ .../client/spi/KubernetesClientBuildItem.java | 18 +++++ extensions/kubernetes/deployment/pom.xml | 8 +++ .../deployment/DeploymentTarget.java | 15 ++++- .../deployment/KubernetesDeploy.java | 67 ++++++++++++++++++- .../deployment/KubernetesDeployer.java | 35 +++++++--- .../deployment/KubernetesProcessor.java | 4 +- 25 files changed, 468 insertions(+), 111 deletions(-) delete mode 100644 core/deployment/src/main/java/io/quarkus/deployment/pkg/ContainerConfig.java create mode 100644 extensions/container/deployment/src/main/java/io/quarkus/container/deployment/ContainerConfig.java create mode 100644 extensions/container/deployment/src/main/java/io/quarkus/container/deployment/ContainerProcessor.java rename {core/deployment/src/main/java/io/quarkus => extensions/container/deployment/src/main/java/io/quarkus/container}/deployment/util/ImageUtil.java (67%) rename {core/deployment/src/main/java/io/quarkus/deployment/pkg/builditem => extensions/container/spi/src/main/java/io/quarkus/container/spi}/ContainerImageBuildItem.java (87%) rename {core/deployment/src/main/java/io/quarkus/deployment/pkg/builditem => extensions/container/spi/src/main/java/io/quarkus/container/spi}/ContainerImageResultBuildItem.java (93%) create mode 100644 extensions/kubernetes-client/deployment/src/main/java/io/quarkus/kubernetes/client/deployment/KubernetesClientBuildStep.java create mode 100644 extensions/kubernetes-client/runtime/src/main/java/io/quarkus/kubernetes/client/runtime/KubernetesClientUtils.java create mode 100644 extensions/kubernetes-client/spi/pom.xml create mode 100644 extensions/kubernetes-client/spi/src/main/java/io/quarkus/kubernetes/client/spi/KubernetesClientBuildItem.java diff --git a/bom/deployment/pom.xml b/bom/deployment/pom.xml index ffa8a46e0..7d81fe6ee 100644 --- a/bom/deployment/pom.xml +++ b/bom/deployment/pom.xml @@ -478,7 +478,7 @@ io.quarkus - quarkus-docker-spi + quarkus-container-spi ${project.version} @@ -491,6 +491,11 @@ quarkus-kubernetes-spi ${project.version} + + io.quarkus + quarkus-kubernetes-client-spi + ${project.version} + io.quarkus quarkus-kubernetes-client-deployment 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 a80b99576..2431614c0 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,6 @@ 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; @@ -110,7 +109,6 @@ public class QuarkusAugmentor { } chainBuilder.addFinal(GeneratedClassBuildItem.class) .addFinal(GeneratedResourceBuildItem.class) - .addFinal(ContainerImageResultBuildItem.class) .addFinal(DeploymentResultBuildItem.class); for (Consumer i : buildChainCustomizers) { 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 deleted file mode 100644 index 7c10b16cb..000000000 --- a/core/deployment/src/main/java/io/quarkus/deployment/pkg/ContainerConfig.java +++ /dev/null @@ -1,21 +0,0 @@ - -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/extensions/container/deployment/pom.xml b/extensions/container/deployment/pom.xml index d09027f6d..5c9fc344c 100644 --- a/extensions/container/deployment/pom.xml +++ b/extensions/container/deployment/pom.xml @@ -17,6 +17,10 @@ io.quarkus quarkus-core-deployment + + io.quarkus + quarkus-container-spi + diff --git a/extensions/container/deployment/src/main/java/io/quarkus/container/deployment/ContainerConfig.java b/extensions/container/deployment/src/main/java/io/quarkus/container/deployment/ContainerConfig.java new file mode 100644 index 000000000..eb340485f --- /dev/null +++ b/extensions/container/deployment/src/main/java/io/quarkus/container/deployment/ContainerConfig.java @@ -0,0 +1,45 @@ +package io.quarkus.container.deployment; + +import java.util.Optional; + +import io.quarkus.runtime.annotations.ConfigItem; +import io.quarkus.runtime.annotations.ConfigRoot; + +@ConfigRoot +public class ContainerConfig { + + /** + * The container registry to use + */ + @ConfigItem + public Optional registry; + + /** + * The group the container image will be part of + */ + @ConfigItem(defaultValue = "${user.name}") + public String group; + + /** + * The name of the container image. If not set defaults to the application name + */ + @ConfigItem + public Optional name; + + /** + * The tag of the container image. If not set defaults to the application version + */ + @ConfigItem + public Optional tag; + /** + * 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/extensions/container/deployment/src/main/java/io/quarkus/container/deployment/ContainerProcessor.java b/extensions/container/deployment/src/main/java/io/quarkus/container/deployment/ContainerProcessor.java new file mode 100644 index 000000000..6fcb345c8 --- /dev/null +++ b/extensions/container/deployment/src/main/java/io/quarkus/container/deployment/ContainerProcessor.java @@ -0,0 +1,35 @@ +package io.quarkus.container.deployment; + +import io.quarkus.container.deployment.util.ImageUtil; +import io.quarkus.container.spi.ContainerImageBuildItem; +import io.quarkus.deployment.annotations.BuildStep; +import io.quarkus.deployment.builditem.ApplicationInfoBuildItem; +import io.quarkus.deployment.pkg.steps.NativeBuild; + +public class ContainerProcessor { + + private ContainerConfig containerConfig; + + @BuildStep(onlyIfNot = NativeBuild.class) + public ContainerImageBuildItem publishImageInfo(ApplicationInfoBuildItem app) { + String image = ImageUtil.getImage(containerConfig.registry, containerConfig.group, + containerConfig.name.orElse(app.getName()), containerConfig.tag.orElse(app.getVersion())); + return new ContainerImageBuildItem(image); + } + + @BuildStep(onlyIf = NativeBuild.class) + public ContainerImageBuildItem publishNativeImageInfo(ApplicationInfoBuildItem app) { + String image = ImageUtil.getImage(containerConfig.registry, containerConfig.group, + containerConfig.name.orElse(app.getName()), containerConfig.tag.orElse(app.getVersion() + "-native")); + return new ContainerImageBuildItem(image); + } + + public ContainerConfig getContainerConfig() { + return this.containerConfig; + } + + public void setContainerConfig(ContainerConfig containerConfig) { + this.containerConfig = containerConfig; + } + +} 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 index 1b0d5acc8..a33ad865d 100644 --- 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 @@ -1,13 +1,23 @@ package io.quarkus.container.deployment; +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; import java.util.function.BooleanSupplier; +import java.util.function.Function; + +import org.jboss.logging.Logger; -import io.quarkus.deployment.pkg.ContainerConfig; import io.quarkus.deployment.util.ExecUtil; public class DockerBuild implements BooleanSupplier { + private static final Logger LOGGER = Logger.getLogger(DockerBuild.class.getName()); + private static boolean daemonFound = false; + private final ContainerConfig containerConfig; DockerBuild(ContainerConfig containerConfig) { @@ -17,12 +27,48 @@ public class DockerBuild implements BooleanSupplier { @Override public boolean getAsBoolean() { if (containerConfig.build) { + //No need to perform the check multiple times. + if (daemonFound) { + return true; + } try { - return ExecUtil.exec("docker", "version"); + OutputFilter filter = new OutputFilter(); + if (ExecUtil.exec(new File("."), filter, "docker", "version", "--format", "'{{.Server.Version}}'")) { + LOGGER.info("Docker daemon found! Version:" + filter.getOutput()); + daemonFound = true; + return true; + } else { + LOGGER.warn("Could not connect to docker daemon!"); + return false; + } } catch (Exception e) { + LOGGER.warn("Could not connect to docker daemon!"); return false; } } return false; } + + public static class OutputFilter implements Function { + private final StringBuilder builder = new StringBuilder(); + + @Override + public Runnable apply(InputStream is) { + return () -> { + try (InputStreamReader isr = new InputStreamReader(is); + BufferedReader reader = new BufferedReader(isr)) { + + for (String line = reader.readLine(); line != null; line = reader.readLine()) { + builder.append(line); + } + } catch (IOException e) { + throw new RuntimeException("Error reading stream.", e); + } + }; + } + + public String getOutput() { + return builder.toString(); + } + } } 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 index d50eea474..11ea62914 100644 --- 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 @@ -8,22 +8,27 @@ import java.io.InputStreamReader; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardCopyOption; +import java.util.HashMap; import java.util.Optional; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Function; +import javax.inject.Inject; + import org.jboss.logging.Logger; +import io.quarkus.container.deployment.util.ImageUtil; +import io.quarkus.container.spi.ContainerImageBuildItem; +import io.quarkus.container.spi.ContainerImageResultBuildItem; +import io.quarkus.deployment.annotations.BuildProducer; 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.ArtifactResultBuildItem; 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 { @@ -31,13 +36,14 @@ public class DockerBuildStep { private static final String DOCKERFILE_JVM = "Dockerfile.jvm"; private static final String DOCKERFILE_NATIVE = "Dockerfile.native"; + @Inject + BuildProducer artifact; + @BuildStep(onlyIf = DockerBuild.class, onlyIfNot = NativeBuild.class) public ContainerImageResultBuildItem dockerBuildFromJar(ApplicationInfoBuildItem app, OutputTargetBuildItem out, - Optional dockerImage, - JarBuildItem artifact) { + ContainerImageBuildItem containerImage, JarBuildItem jar) { 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(), @@ -45,19 +51,21 @@ public class DockerBuildStep { "docker", "build", "-f", dockerFile.resolve(DOCKERFILE_JVM).toAbsolutePath().toString(), - "-t", image, + "-t", containerImage.getImage(), out.getOutputDirectory().toAbsolutePath().toString()); - return new ContainerImageResultBuildItem(reader.getImageId(), ImageUtil.getRepository(image), ImageUtil.getTag(image)); + artifact.produce(new ArtifactResultBuildItem(null, "jar-container", new HashMap())); + return new ContainerImageResultBuildItem(reader.getImageId(), ImageUtil.getRepository(containerImage.getImage()), + ImageUtil.getTag(containerImage.getImage())); } @BuildStep(onlyIf = { DockerBuild.class, NativeBuild.class }) public ContainerImageResultBuildItem dockerBuildFromNativeImage(ApplicationInfoBuildItem app, + ContainerImageBuildItem containerImage, 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(), @@ -65,9 +73,11 @@ public class DockerBuildStep { "docker", "build", "-f", dockerFile.resolve(DOCKERFILE_NATIVE).toAbsolutePath().toString(), - "-t", image, + "-t", containerImage.getImage(), out.getOutputDirectory().toAbsolutePath().toString()); - return new ContainerImageResultBuildItem(reader.getImageId(), ImageUtil.getRepository(image), ImageUtil.getTag(image)); + artifact.produce(new ArtifactResultBuildItem(null, "native-container", new HashMap())); + return new ContainerImageResultBuildItem(reader.getImageId(), ImageUtil.getRepository(containerImage.getImage()), + ImageUtil.getTag(containerImage.getImage())); } private Path extractDockerfile(String resource) { @@ -120,4 +130,5 @@ public class DockerBuildStep { }; } } + } diff --git a/core/deployment/src/main/java/io/quarkus/deployment/util/ImageUtil.java b/extensions/container/deployment/src/main/java/io/quarkus/container/deployment/util/ImageUtil.java similarity index 67% rename from core/deployment/src/main/java/io/quarkus/deployment/util/ImageUtil.java rename to extensions/container/deployment/src/main/java/io/quarkus/container/deployment/util/ImageUtil.java index f58385f73..78d3c5626 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/util/ImageUtil.java +++ b/extensions/container/deployment/src/main/java/io/quarkus/container/deployment/util/ImageUtil.java @@ -1,11 +1,37 @@ -package io.quarkus.deployment.util; +package io.quarkus.container.deployment.util; + +import java.util.Optional; public class ImageUtil { private static final String SLASH = "/"; private static final String COLN = ":"; + /** + * Create an image from the individual parts. + * + * @param registry The registry. + * @param repository The repository. + * @param name The name. + * @param tag The tag. + * @return The image. + */ + public static String getImage(Optional registry, String repository, String name, String tag) { + if (name == null || name.isEmpty()) { + throw new IllegalArgumentException("Docker image name cannot be null!"); + } + if (tag == null || tag.isEmpty()) { + throw new IllegalArgumentException("Docker image tag cannot be null!"); + } + StringBuilder sb = new StringBuilder(); + registry.ifPresent(r -> sb.append(r).append(SLASH)); + sb.append(repository).append(SLASH); + + sb.append(name).append(COLN).append(tag); + return sb.toString(); + } + /** * Return the registry part of the docker image. * diff --git a/extensions/container/pom.xml b/extensions/container/pom.xml index 51fbb8ae6..dce9c3896 100644 --- a/extensions/container/pom.xml +++ b/extensions/container/pom.xml @@ -3,11 +3,11 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> - quarkus-build-parent + quarkus-container-parent io.quarkus 999-SNAPSHOT - ../../build-parent/pom.xml + 4.0.0 quarkus-container-parent diff --git a/core/deployment/src/main/java/io/quarkus/deployment/pkg/builditem/ContainerImageBuildItem.java b/extensions/container/spi/src/main/java/io/quarkus/container/spi/ContainerImageBuildItem.java similarity index 87% rename from core/deployment/src/main/java/io/quarkus/deployment/pkg/builditem/ContainerImageBuildItem.java rename to extensions/container/spi/src/main/java/io/quarkus/container/spi/ContainerImageBuildItem.java index ef31d36c6..7d2b709ae 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/pkg/builditem/ContainerImageBuildItem.java +++ b/extensions/container/spi/src/main/java/io/quarkus/container/spi/ContainerImageBuildItem.java @@ -1,5 +1,5 @@ -package io.quarkus.deployment.pkg.builditem; +package io.quarkus.container.spi; import io.quarkus.builder.item.SimpleBuildItem; diff --git a/core/deployment/src/main/java/io/quarkus/deployment/pkg/builditem/ContainerImageResultBuildItem.java b/extensions/container/spi/src/main/java/io/quarkus/container/spi/ContainerImageResultBuildItem.java similarity index 93% rename from core/deployment/src/main/java/io/quarkus/deployment/pkg/builditem/ContainerImageResultBuildItem.java rename to extensions/container/spi/src/main/java/io/quarkus/container/spi/ContainerImageResultBuildItem.java index 9fecdc8a1..d003dfd76 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/pkg/builditem/ContainerImageResultBuildItem.java +++ b/extensions/container/spi/src/main/java/io/quarkus/container/spi/ContainerImageResultBuildItem.java @@ -1,5 +1,4 @@ - -package io.quarkus.deployment.pkg.builditem; +package io.quarkus.container.spi; import io.quarkus.builder.item.SimpleBuildItem; @@ -26,5 +25,4 @@ public final class ContainerImageResultBuildItem extends SimpleBuildItem { public String getTag() { return this.tag; } - } diff --git a/extensions/kubernetes-client/deployment/pom.xml b/extensions/kubernetes-client/deployment/pom.xml index ff203ba0e..e8ec3f712 100644 --- a/extensions/kubernetes-client/deployment/pom.xml +++ b/extensions/kubernetes-client/deployment/pom.xml @@ -24,6 +24,10 @@ io.quarkus quarkus-kubernetes-client + + + io.quarkus + quarkus-kubernetes-client-spi io.quarkus diff --git a/extensions/kubernetes-client/deployment/src/main/java/io/quarkus/kubernetes/client/deployment/KubernetesClientBuildStep.java b/extensions/kubernetes-client/deployment/src/main/java/io/quarkus/kubernetes/client/deployment/KubernetesClientBuildStep.java new file mode 100644 index 000000000..202cbab8a --- /dev/null +++ b/extensions/kubernetes-client/deployment/src/main/java/io/quarkus/kubernetes/client/deployment/KubernetesClientBuildStep.java @@ -0,0 +1,17 @@ +package io.quarkus.kubernetes.client.deployment; + +import static io.quarkus.kubernetes.client.runtime.KubernetesClientUtils.*; + +import io.quarkus.deployment.annotations.BuildStep; +import io.quarkus.kubernetes.client.runtime.KubernetesClientBuildConfig; +import io.quarkus.kubernetes.client.spi.KubernetesClientBuildItem; + +public class KubernetesClientBuildStep { + + private KubernetesClientBuildConfig buildConfig; + + @BuildStep + public KubernetesClientBuildItem process() { + return new KubernetesClientBuildItem(createClient(buildConfig)); + } +} diff --git a/extensions/kubernetes-client/pom.xml b/extensions/kubernetes-client/pom.xml index b1758dcf7..f297901b8 100644 --- a/extensions/kubernetes-client/pom.xml +++ b/extensions/kubernetes-client/pom.xml @@ -16,5 +16,6 @@ deployment runtime + spi diff --git a/extensions/kubernetes-client/runtime/src/main/java/io/quarkus/kubernetes/client/runtime/KubernetesClientBuildConfig.java b/extensions/kubernetes-client/runtime/src/main/java/io/quarkus/kubernetes/client/runtime/KubernetesClientBuildConfig.java index c41980955..73aac1329 100644 --- a/extensions/kubernetes-client/runtime/src/main/java/io/quarkus/kubernetes/client/runtime/KubernetesClientBuildConfig.java +++ b/extensions/kubernetes-client/runtime/src/main/java/io/quarkus/kubernetes/client/runtime/KubernetesClientBuildConfig.java @@ -14,138 +14,139 @@ public class KubernetesClientBuildConfig { * Whether or not the client should trust a self signed certificate if so presented by the API server */ @ConfigItem(defaultValue = "false") - boolean trustCerts; + public boolean trustCerts; /** * URL of the Kubernetes API server */ @ConfigItem - Optional masterUrl; + public Optional masterUrl; /** * Default namespace to use */ @ConfigItem - Optional namespace; + public Optional namespace; /** * CA certificate file */ @ConfigItem - Optional caCertFile; + public Optional caCertFile; /** * CA certificate data */ @ConfigItem - Optional caCertData; + public Optional caCertData; /** * Client certificate file */ @ConfigItem - Optional clientCertFile; + public Optional clientCertFile; /** * Client certificate data */ @ConfigItem - Optional clientCertData; + public Optional clientCertData; /** * Client key file */ @ConfigItem - Optional clientKeyFile; + public Optional clientKeyFile; /** * Client key data */ @ConfigItem - Optional clientKeyData; + public Optional clientKeyData; /** * Client key algorithm */ @ConfigItem - Optional clientKeyAlgo; + public Optional clientKeyAlgo; /** * Client key passphrase */ @ConfigItem - Optional clientKeyPassphrase; + public Optional clientKeyPassphrase; /** * Kubernetes auth username */ @ConfigItem - Optional username; + public Optional username; /** * Kubernetes auth password */ @ConfigItem - Optional password; + public Optional password; /** * Watch reconnect interval */ @ConfigItem(defaultValue = "PT1S") // default lifted from Kubernetes Client - Duration watchReconnectInterval; + public Duration watchReconnectInterval; /** * Maximum reconnect attempts in case of watch failure * By default there is no limit to the number of reconnect attempts */ @ConfigItem(defaultValue = "-1") // default lifted from Kubernetes Client - int watchReconnectLimit; + public int watchReconnectLimit; /** * Maximum amount of time to wait for a connection with the API server to be established */ @ConfigItem(defaultValue = "PT10S") // default lifted from Kubernetes Client - Duration connectionTimeout; + public Duration connectionTimeout; /** * Maximum amount of time to wait for a request to the API server to be completed */ @ConfigItem(defaultValue = "PT10S") // default lifted from Kubernetes Client - Duration requestTimeout; + public Duration requestTimeout; /** * Maximum amount of time in milliseconds to wait for a rollout to be completed */ @ConfigItem(defaultValue = "PT15M") // default lifted from Kubernetes Client - Duration rollingTimeout; + public Duration rollingTimeout; /** * HTTP proxy used to access the Kubernetes API server */ @ConfigItem - Optional httpProxy; + public Optional httpProxy; /** * HTTPS proxy used to access the Kubernetes API server */ @ConfigItem - Optional httpsProxy; + public Optional httpsProxy; /** * Proxy username */ @ConfigItem - Optional proxyUsername; + public Optional proxyUsername; /** * Proxy password */ @ConfigItem - Optional proxyPassword; + public Optional proxyPassword; /** * IP addresses or hosts to exclude from proxying */ @ConfigItem - Optional noProxy; + public Optional noProxy; + } diff --git a/extensions/kubernetes-client/runtime/src/main/java/io/quarkus/kubernetes/client/runtime/KubernetesClientProducer.java b/extensions/kubernetes-client/runtime/src/main/java/io/quarkus/kubernetes/client/runtime/KubernetesClientProducer.java index 3d47386ab..7382a01d7 100644 --- a/extensions/kubernetes-client/runtime/src/main/java/io/quarkus/kubernetes/client/runtime/KubernetesClientProducer.java +++ b/extensions/kubernetes-client/runtime/src/main/java/io/quarkus/kubernetes/client/runtime/KubernetesClientProducer.java @@ -5,7 +5,6 @@ import javax.enterprise.inject.Produces; import javax.inject.Singleton; import io.fabric8.kubernetes.client.Config; -import io.fabric8.kubernetes.client.ConfigBuilder; import io.fabric8.kubernetes.client.DefaultKubernetesClient; import io.fabric8.kubernetes.client.KubernetesClient; import io.quarkus.arc.DefaultBean; @@ -19,33 +18,7 @@ public class KubernetesClientProducer { @Singleton @Produces public Config config() { - Config base = Config.autoConfigure(null); - return new ConfigBuilder(base) - .withTrustCerts(buildConfig.trustCerts) - .withWatchReconnectInterval((int) buildConfig.watchReconnectInterval.toMillis()) - .withWatchReconnectLimit(buildConfig.watchReconnectLimit) - .withConnectionTimeout((int) buildConfig.connectionTimeout.toMillis()) - .withRequestTimeout((int) buildConfig.requestTimeout.toMillis()) - .withRollingTimeout(buildConfig.rollingTimeout.toMillis()) - .withMasterUrl(buildConfig.masterUrl.orElse(base.getMasterUrl())) - .withNamespace(buildConfig.namespace.orElse(base.getNamespace())) - .withUsername(buildConfig.username.orElse(base.getUsername())) - .withPassword(buildConfig.password.orElse(base.getPassword())) - .withCaCertFile(buildConfig.caCertFile.orElse(base.getCaCertFile())) - .withCaCertData(buildConfig.caCertData.orElse(base.getCaCertData())) - .withClientCertFile(buildConfig.clientCertFile.orElse(base.getClientCertFile())) - .withClientCertData(buildConfig.clientCertData.orElse(base.getClientCertData())) - .withClientKeyFile(buildConfig.clientKeyFile.orElse(base.getClientKeyFile())) - .withClientKeyData(buildConfig.clientKeyData.orElse(base.getClientKeyData())) - .withClientKeyPassphrase(buildConfig.clientKeyPassphrase.orElse(base.getClientKeyPassphrase())) - .withClientKeyAlgo(buildConfig.clientKeyAlgo.orElse(base.getClientKeyAlgo())) - .withHttpProxy(buildConfig.httpProxy.orElse(base.getHttpProxy())) - .withHttpsProxy(buildConfig.httpsProxy.orElse(base.getHttpsProxy())) - .withProxyUsername(buildConfig.proxyUsername.orElse(base.getProxyUsername())) - .withProxyPassword(buildConfig.proxyPassword.orElse(base.getProxyPassword())) - .withNoProxy(buildConfig.noProxy.isPresent() ? buildConfig.noProxy.get() : base.getNoProxy()) - .build(); - + return KubernetesClientUtils.createConfig(buildConfig); } @DefaultBean diff --git a/extensions/kubernetes-client/runtime/src/main/java/io/quarkus/kubernetes/client/runtime/KubernetesClientUtils.java b/extensions/kubernetes-client/runtime/src/main/java/io/quarkus/kubernetes/client/runtime/KubernetesClientUtils.java new file mode 100644 index 000000000..7e73ad3ec --- /dev/null +++ b/extensions/kubernetes-client/runtime/src/main/java/io/quarkus/kubernetes/client/runtime/KubernetesClientUtils.java @@ -0,0 +1,42 @@ +package io.quarkus.kubernetes.client.runtime; + +import io.fabric8.kubernetes.client.Config; +import io.fabric8.kubernetes.client.ConfigBuilder; +import io.fabric8.kubernetes.client.DefaultKubernetesClient; +import io.fabric8.kubernetes.client.KubernetesClient; + +public class KubernetesClientUtils { + + public static Config createConfig(KubernetesClientBuildConfig buildConfig) { + Config base = new Config(); + return new ConfigBuilder() + .withTrustCerts(buildConfig.trustCerts) + .withWatchReconnectInterval((int) buildConfig.watchReconnectInterval.toMillis()) + .withWatchReconnectLimit(buildConfig.watchReconnectLimit) + .withConnectionTimeout((int) buildConfig.connectionTimeout.toMillis()) + .withRequestTimeout((int) buildConfig.requestTimeout.toMillis()) + .withRollingTimeout(buildConfig.rollingTimeout.toMillis()) + .withMasterUrl(buildConfig.masterUrl.orElse(base.getMasterUrl())) + .withNamespace(buildConfig.namespace.orElse(base.getNamespace())) + .withUsername(buildConfig.username.orElse(base.getUsername())) + .withPassword(buildConfig.password.orElse(base.getPassword())) + .withCaCertFile(buildConfig.caCertFile.orElse(base.getCaCertFile())) + .withCaCertData(buildConfig.caCertData.orElse(base.getCaCertData())) + .withClientCertFile(buildConfig.clientCertFile.orElse(base.getClientCertFile())) + .withClientCertData(buildConfig.clientCertData.orElse(base.getClientCertData())) + .withClientKeyFile(buildConfig.clientKeyFile.orElse(base.getClientKeyFile())) + .withClientKeyData(buildConfig.clientKeyData.orElse(base.getClientKeyData())) + .withClientKeyPassphrase(buildConfig.clientKeyPassphrase.orElse(base.getClientKeyPassphrase())) + .withClientKeyAlgo(buildConfig.clientKeyAlgo.orElse(base.getClientKeyAlgo())) + .withHttpProxy(buildConfig.httpProxy.orElse(base.getHttpProxy())) + .withHttpsProxy(buildConfig.httpsProxy.orElse(base.getHttpsProxy())) + .withProxyUsername(buildConfig.proxyUsername.orElse(base.getProxyUsername())) + .withProxyPassword(buildConfig.proxyPassword.orElse(base.getProxyPassword())) + .withNoProxy(buildConfig.noProxy.isPresent() ? buildConfig.noProxy.get() : base.getNoProxy()) + .build(); + } + + public static KubernetesClient createClient(KubernetesClientBuildConfig buildConfig) { + return new DefaultKubernetesClient(createConfig(buildConfig)); + } +} diff --git a/extensions/kubernetes-client/spi/pom.xml b/extensions/kubernetes-client/spi/pom.xml new file mode 100644 index 000000000..5e408e285 --- /dev/null +++ b/extensions/kubernetes-client/spi/pom.xml @@ -0,0 +1,57 @@ + + + quarkus-kubernetes-parent + io.quarkus + 999-SNAPSHOT + + 4.0.0 + + quarkus-kubernetes-client-spi + Quarkus - Kubernetes Client - SPI + Extensions that use the Kubernetes client, use this module to + configure the client instance + + + + io.quarkus + quarkus-core-deployment + + + io.fabric8 + kubernetes-client + + + javax.annotation + javax.annotation-api + + + jakarta.xml.bind + jakarta.xml.bind-api + + + javax.xml.bind + jaxb-api + + + + + + + + + maven-compiler-plugin + + + + io.quarkus + quarkus-extension-processor + ${project.version} + + + + + + + diff --git a/extensions/kubernetes-client/spi/src/main/java/io/quarkus/kubernetes/client/spi/KubernetesClientBuildItem.java b/extensions/kubernetes-client/spi/src/main/java/io/quarkus/kubernetes/client/spi/KubernetesClientBuildItem.java new file mode 100644 index 000000000..c8f9971b3 --- /dev/null +++ b/extensions/kubernetes-client/spi/src/main/java/io/quarkus/kubernetes/client/spi/KubernetesClientBuildItem.java @@ -0,0 +1,18 @@ +package io.quarkus.kubernetes.client.spi; + +import io.fabric8.kubernetes.client.KubernetesClient; +import io.quarkus.builder.item.SimpleBuildItem; + +public final class KubernetesClientBuildItem extends SimpleBuildItem { + + private final KubernetesClient client; + + public KubernetesClientBuildItem(KubernetesClient client) { + this.client = client; + } + + public KubernetesClient getClient() { + return this.client; + } + +} diff --git a/extensions/kubernetes/deployment/pom.xml b/extensions/kubernetes/deployment/pom.xml index f65e41809..68fb40276 100644 --- a/extensions/kubernetes/deployment/pom.xml +++ b/extensions/kubernetes/deployment/pom.xml @@ -17,10 +17,18 @@ io.quarkus quarkus-container-deployment + + io.quarkus + quarkus-container-spi + io.quarkus quarkus-kubernetes-spi + + io.quarkus + quarkus-kubernetes-client-deployment + io.dekorate kubernetes-annotations diff --git a/extensions/kubernetes/deployment/src/main/java/io/quarkus/kubernetes/deployment/DeploymentTarget.java b/extensions/kubernetes/deployment/src/main/java/io/quarkus/kubernetes/deployment/DeploymentTarget.java index 5f7213dba..01ab0ecb1 100644 --- a/extensions/kubernetes/deployment/src/main/java/io/quarkus/kubernetes/deployment/DeploymentTarget.java +++ b/extensions/kubernetes/deployment/src/main/java/io/quarkus/kubernetes/deployment/DeploymentTarget.java @@ -3,8 +3,17 @@ package io.quarkus.kubernetes.deployment; public enum DeploymentTarget { - KUBERNETES, - OPENSHIFT, - KNATIVE + KUBERNETES("Deployment"), + OPENSHIFT("DeploymentConfig"), + KNATIVE("Service"); + private String kind; + + DeploymentTarget(String kind) { + this.kind = kind; + } + + public String getKind() { + return this.kind; + } } 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 index b5bc53462..ad5ddd2b2 100644 --- 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 @@ -1,7 +1,18 @@ package io.quarkus.kubernetes.deployment; +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; import java.util.function.BooleanSupplier; +import java.util.function.Function; + +import org.jboss.logging.Logger; import io.quarkus.container.deployment.ContainerConfig; import io.quarkus.container.deployment.DockerBuild.OutputFilter; @@ -9,6 +20,8 @@ import io.quarkus.deployment.util.ExecUtil; public class KubernetesDeploy implements BooleanSupplier { + private final Logger LOGGER = Logger.getLogger(KubernetesDeploy.class); + private KubernetesConfig kubernetesConfig; private ContainerConfig containerConfig; @@ -20,15 +33,65 @@ public class KubernetesDeploy implements BooleanSupplier { @Override public boolean getAsBoolean() { if (containerConfig.deploy) { + OutputFilter filter = new OutputFilter(); try { if (kubernetesConfig.getDeploymentTarget().contains(DeploymentTarget.OPENSHIFT)) { - return ExecUtil.exec("oc", "version"); + if (ExecUtil.exec(new File("."), filter, "oc", "version")) { + Optional version = getServerVersionFromOc(filter.getLines()); + version.ifPresent(v -> LOGGER.info("Found Kubernetes version:" + v)); + return true; + } + } + if (ExecUtil.exec(new File("."), filter, "kubectl", "version")) { + Optional version = getServerVersionFromKubectl(filter.getLines()); + version.ifPresent(v -> LOGGER.info("Found Kubernetes version:" + v)); + return true; } - return ExecUtil.exec("kubectl", "version"); } catch (Exception e) { return false; } } return false; } + + private static Optional getServerVersionFromOc(List lines) { + return lines.stream() + .filter(l -> l.startsWith("kubernetes")) + .map(l -> l.split(" ")) + .filter(a -> a.length > 2) + .map(a -> a[1]) + .findFirst(); + } + + private static Optional getServerVersionFromKubectl(List lines) { + return lines.stream() + .filter(l -> l.startsWith("Server Version")) + .map(l -> l.split("\"")) + .filter(a -> a.length > 5) + .map(a -> a[5]) + .findFirst(); + } + + private static class OutputFilter implements Function { + private final List list = new ArrayList(); + + @Override + public Runnable apply(InputStream is) { + return () -> { + try (InputStreamReader isr = new InputStreamReader(is); + BufferedReader reader = new BufferedReader(isr)) { + + for (String line = reader.readLine(); line != null; line = reader.readLine()) { + list.add(line); + } + } catch (IOException e) { + throw new RuntimeException("Error reading stream.", e); + } + }; + } + + public List getLines() { + return list; + } + } } 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 index 48c9c75b2..313a82550 100644 --- 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 @@ -1,6 +1,13 @@ package io.quarkus.kubernetes.deployment; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.List; +import java.util.Optional; + import org.jboss.logging.Logger; import io.dekorate.deps.kubernetes.api.model.HasMetadata; @@ -13,10 +20,9 @@ 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.ContainerImageResultBuildItem; import io.quarkus.deployment.pkg.builditem.DeploymentResultBuildItem; import io.quarkus.deployment.pkg.builditem.OutputTargetBuildItem; -import io.quarkus.deployment.util.ExecUtil; +import io.quarkus.kubernetes.client.spi.KubernetesClientBuildItem; public class KubernetesDeployer { @@ -29,13 +35,26 @@ public class KubernetesDeployer { 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 kubernetesConfig.getDeploymentTarget().stream().findFirst().map(d -> { + String namespace = Optional.of(kubernetesClient.getClient().getNamespace()).orElse("default"); - return new DeploymentResultBuildItem(null, null); + LOG.info("Deploying to " + d.name().toLowerCase() + "in namespace:" + namespace + "."); + File manifest = outputTarget.getOutputDirectory().resolve("kubernetes").resolve(d.name().toLowerCase() + ".yml") + .toFile(); + try (FileInputStream fis = new FileInputStream(manifest)) { + List resources = kubernetesClient.getClient().load(fis).inNamespace(namespace).createOrReplace(); + HasMetadata m = resources.stream() + .filter(r -> r.getKind().equals(d.getKind())) + .findFirst() + .orElseThrow(() -> new IllegalStateException( + "No " + d.getKind() + " found under: " + manifest.getAbsolutePath())); + return new DeploymentResultBuildItem(m.getMetadata().getName(), m.getMetadata().getLabels()); + } catch (FileNotFoundException e) { + throw new IllegalStateException("Can't find generated kubernetes manifest: " + manifest.getAbsolutePath()); + } catch (IOException e) { + throw new RuntimeException("Error closing file: " + manifest.getAbsolutePath()); + } + }).get(); } public void setKubernetesconfig(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 4ba6b7954..f11544dbc 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,7 +22,6 @@ 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; @@ -36,9 +35,9 @@ 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.container.spi.ContainerImageBuildItem; import io.quarkus.deployment.IsNormal; import io.quarkus.deployment.annotations.BuildProducer; import io.quarkus.deployment.annotations.BuildStep; @@ -47,7 +46,6 @@ 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;