diff --git a/extensions/kubernetes-client/deployment-internal/pom.xml b/extensions/kubernetes-client/deployment-internal/pom.xml index 5e16758ca8d8c..2d2e93e6c06cc 100644 --- a/extensions/kubernetes-client/deployment-internal/pom.xml +++ b/extensions/kubernetes-client/deployment-internal/pom.xml @@ -34,6 +34,21 @@ io.fabric8 kubernetes-client + + io.quarkus + quarkus-devservices-deployment + + + com.dajudge.kindcontainer + kindcontainer + 1.3.0 + + + junit + junit + + + diff --git a/extensions/kubernetes-client/deployment-internal/src/main/java/io/quarkus/kubernetes/client/deployment/DevServicesKubernetesProcessor.java b/extensions/kubernetes-client/deployment-internal/src/main/java/io/quarkus/kubernetes/client/deployment/DevServicesKubernetesProcessor.java new file mode 100644 index 0000000000000..bd8ee6434cb65 --- /dev/null +++ b/extensions/kubernetes-client/deployment-internal/src/main/java/io/quarkus/kubernetes/client/deployment/DevServicesKubernetesProcessor.java @@ -0,0 +1,415 @@ +package io.quarkus.kubernetes.client.deployment; + +import static com.dajudge.kindcontainer.KubernetesVersionEnum.latest; +import static java.nio.charset.StandardCharsets.US_ASCII; +import static java.nio.charset.StandardCharsets.UTF_8; +import static java.util.Collections.singletonList; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.time.Duration; +import java.util.Base64; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.function.Supplier; +import java.util.stream.Stream; + +import org.jboss.logging.Logger; +import org.testcontainers.DockerClientFactory; +import org.testcontainers.containers.ContainerState; + +import com.dajudge.kindcontainer.ApiServerContainer; +import com.dajudge.kindcontainer.ApiServerContainerVersion; +import com.dajudge.kindcontainer.K3sContainer; +import com.dajudge.kindcontainer.K3sContainerVersion; +import com.dajudge.kindcontainer.KindContainer; +import com.dajudge.kindcontainer.KindContainerVersion; +import com.dajudge.kindcontainer.KubernetesContainer; +import com.dajudge.kindcontainer.KubernetesVersionEnum; +import com.dajudge.kindcontainer.client.KubeConfigUtils; +import com.dajudge.kindcontainer.client.config.Cluster; +import com.dajudge.kindcontainer.client.config.ClusterSpec; +import com.dajudge.kindcontainer.client.config.Context; +import com.dajudge.kindcontainer.client.config.ContextSpec; +import com.dajudge.kindcontainer.client.config.KubeConfig; +import com.dajudge.kindcontainer.client.config.User; +import com.dajudge.kindcontainer.client.config.UserSpec; +import com.github.dockerjava.api.DockerClient; +import com.github.dockerjava.api.command.InspectContainerResponse; + +import io.fabric8.kubernetes.client.Config; +import io.quarkus.deployment.Feature; +import io.quarkus.deployment.IsNormal; +import io.quarkus.deployment.annotations.BuildStep; +import io.quarkus.deployment.annotations.BuildSteps; +import io.quarkus.deployment.builditem.CuratedApplicationShutdownBuildItem; +import io.quarkus.deployment.builditem.DevServicesResultBuildItem; +import io.quarkus.deployment.builditem.DevServicesResultBuildItem.RunningDevService; +import io.quarkus.deployment.builditem.DevServicesSharedNetworkBuildItem; +import io.quarkus.deployment.builditem.DockerStatusBuildItem; +import io.quarkus.deployment.builditem.LaunchModeBuildItem; +import io.quarkus.deployment.console.ConsoleInstalledBuildItem; +import io.quarkus.deployment.console.StartupLogCompressor; +import io.quarkus.deployment.dev.devservices.GlobalDevServicesConfig; +import io.quarkus.deployment.logging.LoggingSetupBuildItem; +import io.quarkus.devservices.common.ConfigureUtil; +import io.quarkus.devservices.common.ContainerAddress; +import io.quarkus.devservices.common.ContainerLocator; +import io.quarkus.devservices.common.ContainerShutdownCloseable; +import io.quarkus.kubernetes.client.runtime.KubernetesClientBuildConfig; +import io.quarkus.kubernetes.client.runtime.KubernetesDevServicesBuildTimeConfig; +import io.quarkus.kubernetes.client.runtime.KubernetesDevServicesBuildTimeConfig.Flavor; +import io.quarkus.runtime.configuration.ConfigUtils; + +@BuildSteps(onlyIfNot = IsNormal.class, onlyIf = { GlobalDevServicesConfig.Enabled.class, NoQuarkusTestKubernetesClient.class }) +public class DevServicesKubernetesProcessor { + private static final Logger log = Logger.getLogger(DevServicesKubernetesProcessor.class); + private static final String KUBERNETES_CLIENT_MASTER_URL = "quarkus.kubernetes-client.master-url"; + private static final String DEFAULT_MASTER_URL_ENDING_WITH_SLASH = Config.DEFAULT_MASTER_URL + "/"; + + static final String DEV_SERVICE_LABEL = "quarkus-dev-service-kubernetes"; + static final int KUBERNETES_PORT = 6443; + private static final ContainerLocator KubernetesContainerLocator = new ContainerLocator(DEV_SERVICE_LABEL, KUBERNETES_PORT); + + static volatile RunningDevService devService; + static volatile KubernetesDevServiceCfg cfg; + static volatile boolean first = true; + + @BuildStep + public DevServicesResultBuildItem setupKubernetesDevService( + DockerStatusBuildItem dockerStatusBuildItem, + LaunchModeBuildItem launchMode, + KubernetesClientBuildConfig kubernetesClientBuildTimeConfig, + List devServicesSharedNetworkBuildItem, + Optional consoleInstalledBuildItem, + CuratedApplicationShutdownBuildItem closeBuildItem, + LoggingSetupBuildItem loggingSetupBuildItem, + GlobalDevServicesConfig devServicesConfig) { + + KubernetesDevServiceCfg configuration = getConfiguration(kubernetesClientBuildTimeConfig); + + if (devService != null) { + boolean shouldShutdownTheCluster = !configuration.equals(cfg); + if (!shouldShutdownTheCluster) { + return devService.toBuildItem(); + } + shutdownCluster(); + cfg = null; + } + + StartupLogCompressor compressor = new StartupLogCompressor( + (launchMode.isTest() ? "(test) " : "") + "Kubernetes Dev Services Starting:", + consoleInstalledBuildItem, loggingSetupBuildItem); + try { + devService = startKubernetes(dockerStatusBuildItem, configuration, launchMode, + !devServicesSharedNetworkBuildItem.isEmpty(), + devServicesConfig.timeout); + if (devService == null) { + compressor.closeAndDumpCaptured(); + } else { + compressor.close(); + } + } catch (Throwable t) { + compressor.closeAndDumpCaptured(); + throw new RuntimeException(t); + } + + if (devService == null) { + return null; + } + + // Configure the watch dog + if (first) { + first = false; + Runnable closeTask = () -> { + if (devService != null) { + shutdownCluster(); + } + first = true; + devService = null; + cfg = null; + }; + closeBuildItem.addCloseTask(closeTask, true); + } + cfg = configuration; + + if (devService.isOwner()) { + log.info( + "Dev Services for Kubernetes started. Other Quarkus applications in dev mode will find the " + + "cluster automatically."); + } + + return devService.toBuildItem(); + } + + private void shutdownCluster() { + if (devService != null) { + try { + devService.close(); + } catch (Throwable e) { + log.error("Failed to stop the Kubernetes cluster", e); + } finally { + devService = null; + } + } + } + + private RunningDevService startKubernetes(DockerStatusBuildItem dockerStatusBuildItem, KubernetesDevServiceCfg config, + LaunchModeBuildItem launchMode, boolean useSharedNetwork, Optional timeout) { + if (!config.devServicesEnabled) { + // explicitly disabled + log.debug("Not starting dev services for Kubernetes, as it has been disabled in the config."); + return null; + } + + // Check if kubernetes-client.master-url is set + if (ConfigUtils.isPropertyPresent(KUBERNETES_CLIENT_MASTER_URL)) { + log.info("Not starting dev services for Kubernetes, the quarkus.kubernetes-client.master-url is configured."); + return null; + } + + if (!config.overrideKubeconfig) { + var autoConfigMasterUrl = Config.autoConfigure(null).getMasterUrl(); + if (!DEFAULT_MASTER_URL_ENDING_WITH_SLASH.equals(autoConfigMasterUrl)) { + log.info( + "Not starting dev services for Kubernetes, the kubernetes client is auto-configured. Set quarkus.kubernetes-client.devservices.override-kubeconfig to true to use dev services for Kubernetes."); + return null; + } + } + + if (!dockerStatusBuildItem.isDockerAvailable()) { + log.warn( + "Docker isn't working, please configure the Kubernetes client."); + return null; + } + + final Optional maybeContainerAddress = KubernetesContainerLocator.locateContainer(config.serviceName, + config.shared, + launchMode.getLaunchMode()); + + final Supplier defaultKubernetesClusterSupplier = () -> { + KubernetesContainer container; + switch (config.flavor) { + case API_ONLY: + container = new ApiServerContainer( + config.apiVersion.map(version -> findOrElseThrow(version, ApiServerContainerVersion.class)) + .orElseGet(() -> latest(ApiServerContainerVersion.class))); + break; + case K3S: + container = new K3sContainer( + config.apiVersion.map(version -> findOrElseThrow(version, K3sContainerVersion.class)) + .orElseGet(() -> latest(K3sContainerVersion.class))); + break; + case KIND: + container = new KindContainer( + config.apiVersion.map(version -> findOrElseThrow(version, KindContainerVersion.class)) + .orElseGet(() -> latest(KindContainerVersion.class))); + break; + default: + throw new RuntimeException(); + } + + if (useSharedNetwork) { + ConfigureUtil.configureSharedNetwork(container, "quarkus-kubernetes-client"); + } + if (config.serviceName != null) { + container.withLabel(DevServicesKubernetesProcessor.DEV_SERVICE_LABEL, config.serviceName); + } + timeout.ifPresent(container::withStartupTimeout); + + container.start(); + + KubeConfig kubeConfig = KubeConfigUtils.parseKubeConfig(container.getKubeconfig()); + + return new RunningDevService(Feature.KUBERNETES_CLIENT.getName(), container.getContainerId(), + new ContainerShutdownCloseable(container, Feature.KUBERNETES_CLIENT.getName()), + getKubernetesClientConfigFromKubeConfig(kubeConfig)); + }; + + return maybeContainerAddress + .map(containerAddress -> new RunningDevService(Feature.KUBERNETES_CLIENT.getName(), + containerAddress.getId(), + null, + resolveConfigurationFromRunningContainer(containerAddress))) + .orElseGet(defaultKubernetesClusterSupplier); + } + + > T findOrElseThrow(final String version, final Class versions) { + final String versionWithPrefix = !version.startsWith("v") ? "v" + version : version; + return Stream.of(versions.getEnumConstants()) + .filter(v -> v.descriptor().getKubernetesVersion().equalsIgnoreCase(versionWithPrefix)) + .findFirst() + .orElseThrow(); + } + + private Map getKubernetesClientConfigFromKubeConfig(KubeConfig kubeConfig) { + + ClusterSpec cluster = kubeConfig.getClusters().get(0).getCluster(); + UserSpec user = kubeConfig.getUsers().get(0).getUser(); + return Map.of( + KUBERNETES_CLIENT_MASTER_URL, cluster.getServer(), + "quarkus.kubernetes-client.ca-cert-data", + cluster.getCertificateAuthorityData(), + "quarkus.kubernetes-client.client-cert-data", + user.getClientCertificateData(), + "quarkus.kubernetes-client.client-key-data", user.getClientKeyData(), + "quarkus.kubernetes-client.client-key-algo", Config.getKeyAlgorithm(null, user.getClientKeyData()), + "quarkus.kubernetes-client.namespace", "default"); + } + + private Map resolveConfigurationFromRunningContainer(ContainerAddress containerAddress) { + var dockerClient = DockerClientFactory.lazyClient(); + var container = new RunningContainer(dockerClient, containerAddress); + + return container.getKubeconfig(); + } + + private KubernetesDevServiceCfg getConfiguration(KubernetesClientBuildConfig cfg) { + KubernetesDevServicesBuildTimeConfig devServicesConfig = cfg.devservices; + return new KubernetesDevServiceCfg(devServicesConfig); + } + + private static final class KubernetesDevServiceCfg { + + public boolean devServicesEnabled; + public Flavor flavor; + public Optional apiVersion; + public boolean overrideKubeconfig; + public boolean shared; + public String serviceName; + + public KubernetesDevServiceCfg(KubernetesDevServicesBuildTimeConfig config) { + this.devServicesEnabled = config.enabled; + this.serviceName = config.serviceName; + this.apiVersion = config.apiVersion; + this.overrideKubeconfig = config.overrideKubeconfig; + this.flavor = config.flavor; + this.shared = config.shared; + } + + @Override + public int hashCode() { + return Objects.hash(devServicesEnabled, flavor, apiVersion, overrideKubeconfig, shared, serviceName); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (!(obj instanceof KubernetesDevServiceCfg)) + return false; + KubernetesDevServiceCfg other = (KubernetesDevServiceCfg) obj; + return devServicesEnabled == other.devServicesEnabled && flavor == other.flavor + && Objects.equals(apiVersion, other.apiVersion) && overrideKubeconfig == other.overrideKubeconfig + && shared == other.shared && Objects.equals(serviceName, other.serviceName); + } + } + + private class RunningContainer implements ContainerState { + private static final String KIND_KUBECONFIG = "/etc/kubernetes/admin.conf"; + private static final String K3S_KUBECONFIG = "/etc/rancher/k3s/k3s.yaml"; + private static final String APISERVER = "apiserver"; + private static final String PKI_BASEDIR = "/etc/kubernetes/pki"; + private static final String API_SERVER_CA = PKI_BASEDIR + "/ca.crt"; + private static final String API_SERVER_CERT = PKI_BASEDIR + "/apiserver.crt"; + private static final String API_SERVER_KEY = PKI_BASEDIR + "/apiserver.key"; + + private final DockerClient dockerClient; + + private final InspectContainerResponse containerInfo; + + private final ContainerAddress containerAddress; + + public RunningContainer(DockerClient dockerClient, ContainerAddress containerAddress) { + this.dockerClient = dockerClient; + this.containerAddress = containerAddress; + this.containerInfo = dockerClient.inspectContainerCmd(getContainerId()).exec(); + } + + public Map getKubeconfig() { + var image = getContainerInfo().getConfig().getImage(); + if (image.contains("rancher/k3s")) { + return getKubernetesClientConfigFromKubeConfig( + KubeConfigUtils.parseKubeConfig(KubeConfigUtils.replaceServerInKubeconfig(containerAddress.getUrl(), + getFileContentFromContainer(K3S_KUBECONFIG)))); + } else if (image.contains("kindest/node")) { + return getKubernetesClientConfigFromKubeConfig( + KubeConfigUtils.parseKubeConfig(KubeConfigUtils.replaceServerInKubeconfig(containerAddress.getUrl(), + getFileContentFromContainer(KIND_KUBECONFIG)))); + } else if (image.contains("k8s.gcr.io/kube-apiserver")) { + return getKubernetesClientConfigFromKubeConfig(getKubeconfigFromApiContainer(containerAddress.getUrl())); + } + + throw new RuntimeException(); + } + + protected KubeConfig getKubeconfigFromApiContainer(final String url) { + final Cluster cluster = new Cluster(); + cluster.setName(APISERVER); + cluster.setCluster(new ClusterSpec()); + cluster.getCluster().setServer(url); + cluster.getCluster().setCertificateAuthorityData((base64(getFileContentFromContainer(API_SERVER_CA)))); + final User user = new User(); + user.setName(APISERVER); + user.setUser(new UserSpec()); + user.getUser().setClientKeyData(base64(getFileContentFromContainer(API_SERVER_KEY))); + user.getUser().setClientCertificateData(base64(getFileContentFromContainer(API_SERVER_CERT))); + final Context context = new Context(); + context.setName(APISERVER); + context.setContext(new ContextSpec()); + context.getContext().setCluster(cluster.getName()); + context.getContext().setUser(user.getName()); + final KubeConfig config = new KubeConfig(); + config.setUsers(singletonList(user)); + config.setClusters(singletonList(cluster)); + config.setContexts(singletonList(context)); + config.setCurrentContext(context.getName()); + return config; + } + + private String base64(final String str) { + return Base64.getEncoder().encodeToString(str.getBytes(US_ASCII)); + } + + @Override + public List getExposedPorts() { + return List.of(containerAddress.getPort()); + } + + @Override + public DockerClient getDockerClient() { + return this.dockerClient; + } + + @Override + public InspectContainerResponse getContainerInfo() { + return containerInfo; + } + + @Override + public String getContainerId() { + return this.containerAddress.getId(); + } + + public String getFileContentFromContainer(String containerPath) { + return copyFileFromContainer(containerPath, this::readString); + } + + String readString(final InputStream is) throws IOException { + return new String(readBytes(is), UTF_8); + } + + private byte[] readBytes(final InputStream is) throws IOException { + final byte[] buffer = new byte[1024]; + final ByteArrayOutputStream bos = new ByteArrayOutputStream(); + int read; + while ((read = is.read(buffer)) > 0) { + bos.write(buffer, 0, read); + } + return bos.toByteArray(); + } + } +} diff --git a/extensions/kubernetes-client/deployment-internal/src/main/java/io/quarkus/kubernetes/client/deployment/NoQuarkusTestKubernetesClient.java b/extensions/kubernetes-client/deployment-internal/src/main/java/io/quarkus/kubernetes/client/deployment/NoQuarkusTestKubernetesClient.java new file mode 100644 index 0000000000000..974cc63ceaa35 --- /dev/null +++ b/extensions/kubernetes-client/deployment-internal/src/main/java/io/quarkus/kubernetes/client/deployment/NoQuarkusTestKubernetesClient.java @@ -0,0 +1,16 @@ +package io.quarkus.kubernetes.client.deployment; + +import java.util.Arrays; +import java.util.function.BooleanSupplier; + +class NoQuarkusTestKubernetesClient implements BooleanSupplier { + static final String IO_QUARKUS_TEST_KUBERNETES_CLIENT_PACKAGE = "io.quarkus.test.kubernetes.client"; + static final Boolean IO_QUARKUS_TEST_KUBERNETES_CLIENT_AVAILABLE = Arrays.asList(Package.getPackages()) + .stream() + .map(p -> p.getName()).anyMatch(p -> p.startsWith(IO_QUARKUS_TEST_KUBERNETES_CLIENT_PACKAGE)); + + @Override + public boolean getAsBoolean() { + return !IO_QUARKUS_TEST_KUBERNETES_CLIENT_AVAILABLE; + } +} \ No newline at end of file diff --git a/extensions/kubernetes-client/runtime-internal/src/main/java/io/quarkus/kubernetes/client/runtime/KubernetesClientBuildConfig.java b/extensions/kubernetes-client/runtime-internal/src/main/java/io/quarkus/kubernetes/client/runtime/KubernetesClientBuildConfig.java index 9b8da5f8ae55b..f2f43c544dd61 100644 --- a/extensions/kubernetes-client/runtime-internal/src/main/java/io/quarkus/kubernetes/client/runtime/KubernetesClientBuildConfig.java +++ b/extensions/kubernetes-client/runtime-internal/src/main/java/io/quarkus/kubernetes/client/runtime/KubernetesClientBuildConfig.java @@ -161,4 +161,10 @@ public class KubernetesClientBuildConfig { @ConfigItem(defaultValue = "true") public boolean generateRbac; + /** + * Config for dev services + */ + @ConfigItem + public KubernetesDevServicesBuildTimeConfig devservices; + } diff --git a/extensions/kubernetes-client/runtime-internal/src/main/java/io/quarkus/kubernetes/client/runtime/KubernetesDevServicesBuildTimeConfig.java b/extensions/kubernetes-client/runtime-internal/src/main/java/io/quarkus/kubernetes/client/runtime/KubernetesDevServicesBuildTimeConfig.java new file mode 100644 index 0000000000000..b7f077aa42529 --- /dev/null +++ b/extensions/kubernetes-client/runtime-internal/src/main/java/io/quarkus/kubernetes/client/runtime/KubernetesDevServicesBuildTimeConfig.java @@ -0,0 +1,83 @@ +package io.quarkus.kubernetes.client.runtime; + +import java.util.Optional; + +import io.quarkus.runtime.annotations.ConfigGroup; +import io.quarkus.runtime.annotations.ConfigItem; + +@ConfigGroup +public class KubernetesDevServicesBuildTimeConfig { + + /** + * If dev services for Kubernetes should be used. (default to true) + * + * If this is true and kubernetes client is not configured then a kubernetes cluster + * will be started and will be used. + */ + @ConfigItem(defaultValue = "true") + public boolean enabled; + + /** + * The kubernetes api server version to use. + * + * If not set, dev services for Kubernetes will use the latest supported version of the given flavor. + * see https://github.com/dajudge/kindcontainer/blob/master/k8s-versions.json + */ + @ConfigItem + public Optional apiVersion; + + /** + * The flavor to use (kind, s3 or api-only). Default to api-only. + */ + @ConfigItem(defaultValue = "API_ONLY") + public Flavor flavor; + + /** + * By default, if a kubeconfig is found, dev services for Kubernetes will not start. + * Set this to true to override the kubeconfig config. + */ + @ConfigItem(defaultValue = "false") + public boolean overrideKubeconfig; + + /** + * Indicates if the Kubernetes cluster managed by Quarkus Dev Services is shared. + * When shared, Quarkus looks for running containers using label-based service discovery. + * If a matching container is found, it is used, and so a second one is not started. + * Otherwise, Dev Services for Kubernetes starts a new container. + *

+ * The discovery uses the {@code quarkus-dev-service-kubernetes} label. + * The value is configured using the {@code service-name} property. + *

+ * Container sharing is only used in dev mode. + */ + @ConfigItem(defaultValue = "true") + public boolean shared; + + /** + * The value of the {@code quarkus-dev-service-kubernetes} label attached to the started container. + * This property is used when {@code shared} is set to {@code true}. + * In this case, before starting a container, Dev Services for Kubernetes looks for a container with the + * {@code quarkus-dev-service-kubernetes} label + * set to the configured value. If found, it will use this container instead of starting a new one. Otherwise, it + * starts a new container with the {@code quarkus-dev-service-kubernetes} label set to the specified value. + *

+ * This property is used when you need multiple shared Kubernetes clusters. + */ + @ConfigItem(defaultValue = "kubernetes") + public String serviceName; + + public static enum Flavor { + /** + * kind (needs priviledge docker) + */ + KIND, + /** + * k3s (needs priviledge docker) + */ + K3S, + /** + * api only + */ + API_ONLY; + } +} diff --git a/integration-tests/kubernetes-client-devservices/pom.xml b/integration-tests/kubernetes-client-devservices/pom.xml new file mode 100644 index 0000000000000..d78c7c04966d5 --- /dev/null +++ b/integration-tests/kubernetes-client-devservices/pom.xml @@ -0,0 +1,118 @@ + + + 4.0.0 + + io.quarkus + quarkus-integration-tests-parent + 999-SNAPSHOT + ../pom.xml + + quarkus-integration-test-kubernetes-client-devservices + Quarkus - Integration Tests - Kubernetes Client DevService + + + + io.quarkus + quarkus-kubernetes-client + + + io.quarkus + quarkus-arc + + + io.quarkus + quarkus-junit5 + test + + + + io.quarkus + quarkus-kubernetes-client-deployment + ${project.version} + pom + test + + + * + * + + + + + io.quarkus + quarkus-arc-deployment + ${project.version} + pom + test + + + * + * + + + + + + + + + src/main/resources + true + + + + + maven-surefire-plugin + + true + + + + maven-failsafe-plugin + + true + + + + io.quarkus + quarkus-maven-plugin + + + + build + + + + + + + + + + + test-kubernetes-client + + + test-containers + + + + + + maven-surefire-plugin + + false + + + + maven-failsafe-plugin + + false + + + + + + + + \ No newline at end of file diff --git a/integration-tests/kubernetes-client-devservices/src/main/resources/application.properties b/integration-tests/kubernetes-client-devservices/src/main/resources/application.properties new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/integration-tests/kubernetes-client-devservices/src/test/java/io/quarkus/kubernetes/client/devservices/it/DevServicesKubernetesITest.java b/integration-tests/kubernetes-client-devservices/src/test/java/io/quarkus/kubernetes/client/devservices/it/DevServicesKubernetesITest.java new file mode 100644 index 0000000000000..a30211e97fa92 --- /dev/null +++ b/integration-tests/kubernetes-client-devservices/src/test/java/io/quarkus/kubernetes/client/devservices/it/DevServicesKubernetesITest.java @@ -0,0 +1,27 @@ +package io.quarkus.kubernetes.client.devservices.it; + +import javax.inject.Inject; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import io.fabric8.kubernetes.client.KubernetesClient; +import io.quarkus.kubernetes.client.devservices.it.profiles.DevServiceKubernetes; +import io.quarkus.test.junit.QuarkusTest; +import io.quarkus.test.junit.TestProfile; + +@QuarkusTest +@TestProfile(DevServiceKubernetes.class) +public class DevServicesKubernetesITest { + + @Inject + KubernetesClient kubernetesClient; + + @Test + @DisplayName("given kubernetes container must communicate with it and return its version") + public void shouldReturnAllKeys() { + Assertions.assertEquals("v" + DevServiceKubernetes.API_VERSION, + kubernetesClient.getKubernetesVersion().getGitVersion()); + } +} diff --git a/integration-tests/kubernetes-client-devservices/src/test/java/io/quarkus/kubernetes/client/devservices/it/profiles/DevServiceKubernetes.java b/integration-tests/kubernetes-client-devservices/src/test/java/io/quarkus/kubernetes/client/devservices/it/profiles/DevServiceKubernetes.java new file mode 100644 index 0000000000000..df99a5694fefa --- /dev/null +++ b/integration-tests/kubernetes-client-devservices/src/test/java/io/quarkus/kubernetes/client/devservices/it/profiles/DevServiceKubernetes.java @@ -0,0 +1,17 @@ +package io.quarkus.kubernetes.client.devservices.it.profiles; + +import java.util.Collections; +import java.util.Map; + +import io.quarkus.test.junit.QuarkusTestProfile; + +public class DevServiceKubernetes implements QuarkusTestProfile { + + public static final String API_VERSION = "1.24.1"; + + @Override + public Map getConfigOverrides() { + + return Collections.singletonMap("quarkus.kubernetes-client.devservices.api-version", API_VERSION); + } +} diff --git a/integration-tests/pom.xml b/integration-tests/pom.xml index 99dbb209ec2b2..558f3223547a0 100644 --- a/integration-tests/pom.xml +++ b/integration-tests/pom.xml @@ -245,6 +245,7 @@ container-image kubernetes kubernetes-client + kubernetes-client-devservices kubernetes-service-binding-jdbc kubernetes-service-binding-reactive openshift-client