diff --git a/docs/src/main/asciidoc/kubernetes-dev-services.adoc b/docs/src/main/asciidoc/kubernetes-dev-services.adoc index 7e5b761285b1d..137ef6d036480 100644 --- a/docs/src/main/asciidoc/kubernetes-dev-services.adoc +++ b/docs/src/main/asciidoc/kubernetes-dev-services.adoc @@ -88,6 +88,21 @@ And then add the following property to your `application.properties`: quarkus.kubernetes-client.devservices.manifests=kubernetes/namespace.yaml ``` +When applying multiple properties, you can apply them as: +``` +quarkus.kubernetes-client.devservices.manifests=kubernetes/first_manifest.yaml,kubernetes/second_manifest.yaml +``` + +It is also possible to apply manifests from a URL. So the following syntax would also be possible: +``` +quarkus.kubernetes-client.devservices.manifests=https://example.com/kubernetes/namespace.yaml +``` + +NOTE: Quarkus will apply the manifests in the order they are specified in the list. + +NOTE: Quarkus will await resources such as deployments to be ready before moving on to the next manifest. +All applied resources are guaranteed to be ready by the time your application finishes startup. + === Deploying helm charts `quarkus.kubernetes-client.devservices.manifests` only supports manifests. If you want to deploy a helm chart, you can use the `k3s` flavor and deploy a `HelmChart` manifest. See https://docs.k3s.io/helm for more information on how to use helm charts with k3s. diff --git a/extensions/kubernetes-client/deployment/src/main/java/io/quarkus/kubernetes/client/deployment/DevServicesKubernetesProcessor.java b/extensions/kubernetes-client/deployment/src/main/java/io/quarkus/kubernetes/client/deployment/DevServicesKubernetesProcessor.java index 63833b662dafc..0fb83ffe9a1cc 100644 --- a/extensions/kubernetes-client/deployment/src/main/java/io/quarkus/kubernetes/client/deployment/DevServicesKubernetesProcessor.java +++ b/extensions/kubernetes-client/deployment/src/main/java/io/quarkus/kubernetes/client/deployment/DevServicesKubernetesProcessor.java @@ -11,12 +11,11 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; +import java.net.MalformedURLException; +import java.net.URL; 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.*; +import java.util.concurrent.TimeUnit; import java.util.function.Function; import java.util.function.Supplier; import java.util.stream.Collectors; @@ -45,8 +44,12 @@ import com.github.dockerjava.api.DockerClient; import com.github.dockerjava.api.command.InspectContainerResponse; -import io.fabric8.kubernetes.api.model.HasMetadata; +import io.fabric8.kubernetes.api.model.*; +import io.fabric8.kubernetes.api.model.apps.Deployment; +import io.fabric8.kubernetes.api.model.apps.ReplicaSet; +import io.fabric8.kubernetes.api.model.apps.StatefulSet; import io.fabric8.kubernetes.client.*; +import io.fabric8.kubernetes.client.Config; import io.quarkus.deployment.Feature; import io.quarkus.deployment.IsDevServicesSupportedByLaunchMode; import io.quarkus.deployment.annotations.BuildProducer; @@ -196,10 +199,7 @@ public void applyManifests( .withConfig(Config.fromKubeconfig(kubernetesDevServiceInfoBuildItem.getKubeConfig())) .build()) { for (String manifestPath : manifests.get()) { - // Load the manifest from the resources directory - InputStream manifestStream = Thread.currentThread() - .getContextClassLoader() - .getResourceAsStream(manifestPath); + InputStream manifestStream = getManifestStream(manifestPath); if (manifestStream == null) { log.errorf("Could not find manifest file in resources: %s", manifestPath); @@ -210,21 +210,63 @@ public void applyManifests( try { // A single manifest file may contain multiple resources to deploy List resources = client.load(manifestStream).items(); + List resourcesWithReadiness = new ArrayList<>(); if (resources.isEmpty()) { log.warnf("No resources found in manifest: %s", manifestPath); } else { - resources.forEach(resource -> client.resource(resource).create()); + resources.forEach(resource -> { + client.resource(resource).create(); + + if (isReadinessApplicable(resource)) { + resourcesWithReadiness.add(resource); + } + }); + + resourcesWithReadiness.forEach(resource -> { + log.info("Waiting for " + resource.getClass().getSimpleName() + " " + + resource.getMetadata().getName() + + " to be ready..."); + client.resource(resource).waitUntilReady(60, TimeUnit.SECONDS); + }); } } catch (Exception ex) { - log.errorf("Failed to deploy manifest %s: %s", manifestPath, ex.getMessage()); + log.errorf("Failed to apply manifest %s: %s", manifestPath, ex.getMessage()); } } log.infof("Applied manifest %s.", manifestPath); } } catch (Exception e) { - log.error("Failed to create Kubernetes client while trying to deploy manifests.", e); + log.error("Failed to create Kubernetes client while trying to apply manifests.", e); + } + } + + private boolean isReadinessApplicable(HasMetadata item) { + return (item instanceof Deployment || + item instanceof io.fabric8.kubernetes.api.model.extensions.Deployment || + item instanceof ReplicaSet || + item instanceof Pod || + item instanceof ReplicationController || + item instanceof Endpoints || + item instanceof Node || + item instanceof StatefulSet); + } + + private InputStream getManifestStream(String manifestPath) throws IOException { + try { + URL url = new URL(manifestPath); + // For file:// URLs, optionally check if you want to support those or not + return url.openStream(); + } catch (MalformedURLException e) { + // Not a URL, so treat as classpath resource + InputStream stream = Thread.currentThread() + .getContextClassLoader() + .getResourceAsStream(manifestPath); + if (stream == null) { + throw new IOException("Resource not found: " + manifestPath); + } + return stream; } } diff --git a/integration-tests/kubernetes-client-devservices/src/main/resources/application.properties b/integration-tests/kubernetes-client-devservices/src/main/resources/application.properties index 4f50a8d1d3d32..fe4e7a77389c6 100644 --- a/integration-tests/kubernetes-client-devservices/src/main/resources/application.properties +++ b/integration-tests/kubernetes-client-devservices/src/main/resources/application.properties @@ -1 +1 @@ -quarkus.kubernetes-client.devservices.manifests=kubernetes/namespace.yaml +quarkus.kubernetes-client.devservices.manifests=kubernetes/namespace.yaml,https://k8s.io/examples/admin/namespace-dev.yaml diff --git a/integration-tests/kubernetes-client-devservices/src/test/java/io/quarkus/kubernetes/client/devservices/it/DevServicesKubernetesTest.java b/integration-tests/kubernetes-client-devservices/src/test/java/io/quarkus/kubernetes/client/devservices/it/DevServicesKubernetesTest.java index 6a36fb416cb47..f5594610442d6 100644 --- a/integration-tests/kubernetes-client-devservices/src/test/java/io/quarkus/kubernetes/client/devservices/it/DevServicesKubernetesTest.java +++ b/integration-tests/kubernetes-client-devservices/src/test/java/io/quarkus/kubernetes/client/devservices/it/DevServicesKubernetesTest.java @@ -26,8 +26,15 @@ public void shouldReturnAllKeys() { } @Test - @DisplayName("specified manifest must be applied to the cluster by the dev service") - public void manifestIsApplied() { + @DisplayName("specified manifest in the resources folder must be applied to the cluster by the dev service") + public void resourceManifestIsApplied() { Assertions.assertNotNull(kubernetesClient.namespaces().withName("example-namespace").get()); } + + @Test + @DisplayName("specified manifest from a URL must be applied to the cluster by the dev service") + public void urlManifestIsApplied() { + // Applied by https://k8s.io/examples/admin/namespace-dev.yaml + Assertions.assertNotNull(kubernetesClient.namespaces().withName("development").get()); + } }