This is an automated email from the ASF dual-hosted git repository. claudio4j pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/camel.git
The following commit(s) were added to refs/heads/main by this push: new a6107a2132d CAMEL-21835 CAMEL-21841 knative service not working in camel-jbang-plugin-kubernetes (#17508) a6107a2132d is described below commit a6107a2132dcae2fd2259051971284decfc0c4ff Author: Claudio Miranda <clau...@claudius.com.br> AuthorDate: Fri Mar 21 07:19:19 2025 -0300 CAMEL-21835 CAMEL-21841 knative service not working in camel-jbang-plugin-kubernetes (#17508) --- .../modules/ROOT/pages/camel-jbang-kubernetes.adoc | 5 + .../core/commands/kubernetes/ClusterType.java | 3 +- .../core/commands/kubernetes/KubernetesDelete.java | 33 ++++- .../core/commands/kubernetes/KubernetesHelper.java | 21 +++ .../core/commands/kubernetes/KubernetesRun.java | 56 +++++++- .../commands/kubernetes/traits/ContainerTrait.java | 18 ++- .../kubernetes/traits/DeploymentTrait.java | 7 +- .../traits/knative/KnativeServiceTrait.java | 17 +++ .../commands/kubernetes/KubernetesDeleteTest.java | 12 ++ .../kubernetes/KubernetesExportKnativeTest.java | 2 +- .../kubernetes/KubernetesRunCustomTest.java | 142 +++++++++++++++------ 11 files changed, 260 insertions(+), 56 deletions(-) diff --git a/docs/user-manual/modules/ROOT/pages/camel-jbang-kubernetes.adoc b/docs/user-manual/modules/ROOT/pages/camel-jbang-kubernetes.adoc index 2d4494dafe0..cac96c85cec 100644 --- a/docs/user-manual/modules/ROOT/pages/camel-jbang-kubernetes.adoc +++ b/docs/user-manual/modules/ROOT/pages/camel-jbang-kubernetes.adoc @@ -481,6 +481,11 @@ The Knative service trait will create such a resource as part of the Kubernetes NOTE: The `knative-service` trait is disabled by default, you need to enable the Knative service trait with `--trait knative-service.enabled=true` option. Otherwise, the Camel JBang export will always create an arbitrary Kubernetes service resource. +NOTE: If you enable the knative-service trait and deploys to minikube and uses the docker builder to build the container image in the minikube registry addon, it's likely the container image published won't contain the digest value, and https://knative.dev/docs/serving/configuration/deployment/#skipping-tag-resolution[knative-serving has a verification to check the image digest], so the pod will fail to start with the error message `failed to resolve image to digest`, so for development [...] + +WARN: Currently knative-service doesn't work in OpenShift, work is underway to fix it. + + The trait offers following options for customization: [cols="2m,1m,5a"] diff --git a/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/ClusterType.java b/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/ClusterType.java index c30ece52e34..d63e599701b 100644 --- a/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/ClusterType.java +++ b/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/ClusterType.java @@ -39,7 +39,6 @@ public enum ClusterType { if (clusterType == null) { return false; } - - return this == fromName(clusterType); + return this.name().equalsIgnoreCase(clusterType); } } diff --git a/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/KubernetesDelete.java b/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/KubernetesDelete.java index f60790bc02f..c22c3823f88 100644 --- a/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/KubernetesDelete.java +++ b/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/KubernetesDelete.java @@ -23,9 +23,11 @@ import java.util.List; import java.util.Map; import io.fabric8.kubernetes.api.model.StatusDetails; +import io.fabric8.kubernetes.client.dsl.base.ResourceDefinitionContext; import io.fabric8.openshift.client.OpenShiftClient; import org.apache.camel.dsl.jbang.core.commands.CamelJBangMain; import org.apache.camel.dsl.jbang.core.commands.kubernetes.traits.BaseTrait; +import org.apache.camel.util.ObjectHelper; import org.apache.camel.util.StringHelper; import org.codehaus.plexus.util.ExceptionUtils; import picocli.CommandLine; @@ -46,6 +48,9 @@ public class KubernetesDelete extends KubernetesBaseCommand { public Integer doCall() throws Exception { printer().printf("Deleting all resources from app: %s%n", appName); + if (ObjectHelper.isEmpty(namespace)) { + namespace = getKubernetesClient().getNamespace(); + } Map<String, String> labels = new HashMap<>(); // this label is set in KubernetesRun command labels.put(BaseTrait.KUBERNETES_LABEL_MANAGED_BY, "camel-jbang"); @@ -54,23 +59,39 @@ public class KubernetesDelete extends KubernetesBaseCommand { try { // delete the most common resources // delete Deployment cascades to pod - deleteStatuses.addAll(getKubernetesClient().apps().deployments().withLabels(labels).delete()); + deleteStatuses + .addAll(getKubernetesClient().apps().deployments().inNamespace(namespace).withLabels(labels).delete()); // delete service - deleteStatuses.addAll(getKubernetesClient().services().withLabels(labels).delete()); + deleteStatuses.addAll(getKubernetesClient().services().inNamespace(namespace).withLabels(labels).delete()); + // delete knative-services + ResourceDefinitionContext knativeServices = new ResourceDefinitionContext.Builder() + .withGroup("serving.knative.dev") + .withVersion("v1") + .withKind("Service") + .withNamespaced(true) + .build(); + deleteStatuses + .addAll(getKubernetesClient().genericKubernetesResources(knativeServices).inNamespace(namespace) + .withLabels(labels).delete()); + // delete configmap + deleteStatuses.addAll(getKubernetesClient().configMaps().inNamespace(namespace).withLabels(labels).delete()); + // delete secrets + deleteStatuses.addAll(getKubernetesClient().secrets().inNamespace(namespace).withLabels(labels).delete()); ClusterType clusterType = KubernetesHelper.discoverClusterType(); if (ClusterType.OPENSHIFT == clusterType) { // openshift specific: BuildConfig, ImageStreams, Route - BuildConfig casacade delete to Build and ConfigMap OpenShiftClient ocpClient = getKubernetesClient().adapt(OpenShiftClient.class); // BuildConfig - deleteStatuses.addAll(ocpClient.buildConfigs().withLabels(labels).delete()); + deleteStatuses.addAll(ocpClient.buildConfigs().inNamespace(namespace).withLabels(labels).delete()); // ImageStreams - deleteStatuses.addAll(ocpClient.imageStreams().withLabels(labels).delete()); + deleteStatuses.addAll(ocpClient.imageStreams().inNamespace(namespace).withLabels(labels).delete()); // Route - deleteStatuses.addAll(ocpClient.routes().withLabels(labels).delete()); + deleteStatuses.addAll(ocpClient.routes().inNamespace(namespace).withLabels(labels).delete()); } if (deleteStatuses.size() > 0) { deleteStatuses.forEach( - s -> printer().printf("Deleted: %s '%s'%n", StringHelper.capitalize(s.getKind()), s.getName())); + s -> printer().printf("Deleted: %s/%s '%s'%n", s.getGroup(), StringHelper.capitalize(s.getKind()), + s.getName())); } else { printer().println("No deployment found with name: " + appName); } diff --git a/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/KubernetesHelper.java b/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/KubernetesHelper.java index 71fbf2349a5..507d8cb0911 100644 --- a/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/KubernetesHelper.java +++ b/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/KubernetesHelper.java @@ -32,6 +32,8 @@ import com.fasterxml.jackson.databind.MapperFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; import com.fasterxml.jackson.databind.json.JsonMapper; +import io.fabric8.kubernetes.api.model.ConfigMap; +import io.fabric8.kubernetes.api.model.ConfigMapBuilder; import io.fabric8.kubernetes.api.model.GenericKubernetesResource; import io.fabric8.kubernetes.api.model.GenericKubernetesResourceList; import io.fabric8.kubernetes.api.model.Pod; @@ -188,6 +190,25 @@ public final class KubernetesHelper { return minikube; } + // when minikube is used with the registry addon exposed + // the build of images uses docker builder directly from the registry inside minikube + // that doesn't generate the container image digest in the registry + // that poses a problem when deploying a knative-service, since it validates the container image to have a digest + // then it fails with: failed to resolve image to digest + // so, for development purposes we disable this validation in minikube + // https://knative.dev/docs/serving/configuration/deployment/#skipping-tag-resolution + public static void skipKnativeImageTagResolutionInMinikube() { + ConfigMap cm = getKubernetesClient().configMaps().inNamespace("knative-serving").withName("config-deployment").get(); + Map<String, String> data = cm.getData(); + String skipTag = data.get("registries-skipping-tag-resolving"); + if (skipTag == null || !skipTag.contains("localhost:5000")) { + // patch the cm/config-deployment in knative-serving namespace with + // registries-skipping-tag-resolving: localhost:5000 + getKubernetesClient().configMaps().inNamespace("knative-serving").withName("config-deployment").edit( + c -> new ConfigMapBuilder(c).addToData("registries-skipping-tag-resolving", "localhost:5000").build()); + } + } + /** * Sanitize given name to meet Kubernetes resource naming requirements. * diff --git a/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/KubernetesRun.java b/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/KubernetesRun.java index 0fa05b85375..45e17cbb6ed 100644 --- a/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/KubernetesRun.java +++ b/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/KubernetesRun.java @@ -37,6 +37,8 @@ import org.apache.camel.CamelContext; import org.apache.camel.dsl.jbang.core.commands.CamelJBangMain; import org.apache.camel.dsl.jbang.core.commands.CommandHelper; import org.apache.camel.dsl.jbang.core.commands.kubernetes.traits.BaseTrait; +import org.apache.camel.dsl.jbang.core.commands.kubernetes.traits.TraitHelper; +import org.apache.camel.dsl.jbang.core.commands.kubernetes.traits.model.Traits; import org.apache.camel.dsl.jbang.core.common.Printer; import org.apache.camel.dsl.jbang.core.common.RuntimeCompletionCandidates; import org.apache.camel.dsl.jbang.core.common.RuntimeType; @@ -257,7 +259,8 @@ public class KubernetesRun extends KubernetesBaseCommand { description = "Maven/Gradle build properties, ex. --build-property=prop1=foo") List<String> buildProperties = new ArrayList<>(); - @CommandLine.Option(names = { "--disable-auto" }, description = "Disable automatic cluster type detection.") + @CommandLine.Option(names = { "--disable-auto" }, + description = "Disable automatic cluster type detection and automatic settings for cluster.") boolean disableAuto = false; // DevMode/Reload state @@ -297,7 +300,10 @@ public class KubernetesRun extends KubernetesBaseCommand { } if (output != null) { - exit = buildProject(workingDir); + Traits ptraits = TraitHelper.parseTraits(traits); + boolean ksvcEnabled = ptraits.getKnativeService() != null && ptraits.getKnativeService().getEnabled(); + + exit = buildProjectOutput(workingDir); if (exit != 0) { printer().printErr("Project build failed!"); return exit; @@ -305,8 +311,14 @@ public class KubernetesRun extends KubernetesBaseCommand { File manifest; switch (output) { - case "yaml" -> - manifest = KubernetesHelper.resolveKubernetesManifest(clusterType, workingDir + "/target/kubernetes"); + case "yaml" -> { + if (ksvcEnabled) { + // trick the clusterType to be able to read from the jkube source directory + manifest = KubernetesHelper.resolveKubernetesManifest("service", workingDir + "/src/main/jkube"); + } else { + manifest = KubernetesHelper.resolveKubernetesManifest(clusterType, workingDir + "/target/kubernetes"); + } + } case "json" -> manifest = KubernetesHelper.resolveKubernetesManifest(clusterType, workingDir + "/target/kubernetes", "json"); @@ -558,7 +570,8 @@ public class KubernetesRun extends KubernetesBaseCommand { return client; } - private Integer buildProject(String workingDir) throws IOException, InterruptedException { + // build the project locally to generate the artifacts in the target directory, but don't deploy it. + private Integer buildProjectOutput(String workingDir) throws IOException, InterruptedException { printer().println("Building Camel application ..."); // Run build via Maven @@ -586,6 +599,15 @@ public class KubernetesRun extends KubernetesBaseCommand { // suppress maven transfer progress args.add("-ntp"); + Traits ptraits = TraitHelper.parseTraits(traits); + if (ptraits.getKnativeService() != null && ptraits.getKnativeService().getEnabled()) { + // by default jkube creates a Deployment manifest and it doesn't support knative controller yet. + // however when knative-service is enabled the knative-service trait generates a src/main/jkube/service.yml + // and there is no need for the regular Deployment as the knative Service manifest, once deployed + // will generate the regular Deployment, so we have to disable the jkube resources task to not run and not generate the deployment.yml + args.add("-Djkube.skip.resource=true"); + } + args.add("package"); printer().println("Run: " + String.join(" ", args)); @@ -626,6 +648,8 @@ public class KubernetesRun extends KubernetesBaseCommand { if (quiet || !verbose) { args.add("--quiet"); } + // suppress maven transfer progress + args.add("-ntp"); args.add("--file"); args.add(workingDir); @@ -644,7 +668,27 @@ public class KubernetesRun extends KubernetesBaseCommand { boolean isOpenshift = ClusterType.OPENSHIFT.isEqualTo(clusterType); var prefix = isOpenshift ? "oc" : "k8s"; - args.add(prefix + ":deploy"); + Traits ptraits = TraitHelper.parseTraits(traits); + if (ptraits.getKnativeService() != null && ptraits.getKnativeService().getEnabled()) { + // by default jkube creates a Deployment manifest and it doesn't support knative controller yet. + // however when knative-service is enabled the knative-service trait generates a src/main/jkube/service.yml + // and there is no need for the regular Deployment as the knative Service manifest, once deployed + // will generate the regular Deployment, so we have to disable the jkube resources task to not run and not generate the deployment.yml + // apply the knative service manifest and specify the knative service.yml + args.add("-Djkube.skip.resource=true"); + args.add(prefix + ":build"); + args.add(prefix + ":apply"); + if (isOpenshift) { + args.add("-Djkube.openshiftManifest=src/main/jkube/service.yml"); + } else { + args.add("-Djkube.kubernetesManifest=src/main/jkube/service.yml"); + } + if (ClusterType.MINIKUBE.isEqualTo(clusterType) && !disableAuto) { + KubernetesHelper.skipKnativeImageTagResolutionInMinikube(); + } + } else { + args.add(prefix + ":deploy"); + } printer().println("Run: " + String.join(" ", args)); diff --git a/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/traits/ContainerTrait.java b/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/traits/ContainerTrait.java index ca3c292229b..549f7f58241 100644 --- a/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/traits/ContainerTrait.java +++ b/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/traits/ContainerTrait.java @@ -31,6 +31,7 @@ public class ContainerTrait extends BaseTrait { public static final int CONTAINER_TRAIT_ORDER = 1600; public static final int DEFAULT_CONTAINER_PORT = 8080; public static final String DEFAULT_CONTAINER_PORT_NAME = "http"; + public static final String KNATIVE_CONTAINER_PORT_NAME = "h2c"; public ContainerTrait() { super("container", CONTAINER_TRAIT_ORDER); @@ -55,8 +56,11 @@ public class ContainerTrait extends BaseTrait { } if (containerTrait.getPort() != null || context.getService().isPresent() || context.getKnativeService().isPresent()) { + String portName = context.getKnativeService().isPresent() + ? KNATIVE_CONTAINER_PORT_NAME + : Optional.ofNullable(containerTrait.getPortName()).orElse(DEFAULT_CONTAINER_PORT_NAME); container.addToPorts(new ContainerPortBuilder() - .withName(Optional.ofNullable(containerTrait.getPortName()).orElse(DEFAULT_CONTAINER_PORT_NAME)) + .withName(portName) .withContainerPort( Optional.ofNullable(containerTrait.getPort()).map(Long::intValue).orElse(DEFAULT_CONTAINER_PORT)) .withProtocol("TCP") @@ -78,6 +82,18 @@ public class ContainerTrait extends BaseTrait { } container.withResources(resourceRequirementsBuilder.build()); + io.fabric8.kubernetes.api.model.Container cc = container.build(); + context.doWithKnativeServices(s -> s.editOrNewSpec() + .editOrNewTemplate() + .editOrNewMetadata() + .addToLabels(KUBERNETES_LABEL_NAME, context.getName()) + .endMetadata() + .editOrNewSpec() + .addToContainers(cc) + .endSpec() + .endTemplate() + .endSpec()); + context.doWithDeployments(d -> d.editOrNewSpec() .editOrNewTemplate() .editOrNewMetadata() diff --git a/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/traits/DeploymentTrait.java b/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/traits/DeploymentTrait.java index 32bc02578c7..35fa071e68d 100644 --- a/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/traits/DeploymentTrait.java +++ b/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/traits/DeploymentTrait.java @@ -34,7 +34,12 @@ public class DeploymentTrait extends BaseTrait { @Override public boolean configure(Traits traitConfig, TraitContext context) { - return true; + // disable the deployment trait if knative-service is enabled + boolean knEnabled = false; + if (traitConfig.getKnativeService() != null) { + knEnabled = Optional.ofNullable(traitConfig.getKnativeService().getEnabled()).orElse(false); + } + return !knEnabled; } @Override diff --git a/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/traits/knative/KnativeServiceTrait.java b/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/traits/knative/KnativeServiceTrait.java index 0507fe5fe21..d0a2a9c8a6d 100644 --- a/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/traits/knative/KnativeServiceTrait.java +++ b/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/traits/knative/KnativeServiceTrait.java @@ -18,6 +18,7 @@ package org.apache.camel.dsl.jbang.core.commands.kubernetes.traits.knative; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Optional; @@ -26,6 +27,7 @@ import org.apache.camel.dsl.jbang.core.commands.kubernetes.traits.BaseTrait; import org.apache.camel.dsl.jbang.core.commands.kubernetes.traits.ServiceTrait; import org.apache.camel.dsl.jbang.core.commands.kubernetes.traits.TraitContext; import org.apache.camel.dsl.jbang.core.commands.kubernetes.traits.TraitHelper; +import org.apache.camel.dsl.jbang.core.commands.kubernetes.traits.model.Container; import org.apache.camel.dsl.jbang.core.commands.kubernetes.traits.model.KnativeService; import org.apache.camel.dsl.jbang.core.commands.kubernetes.traits.model.Traits; import org.apache.camel.util.ObjectHelper; @@ -94,6 +96,12 @@ public class KnativeServiceTrait extends KnativeBaseTrait { Map<String, String> serviceLabels = new HashMap<>(); serviceLabels.put(BaseTrait.KUBERNETES_LABEL_NAME, context.getName()); + // add the same labels added by jkube, since for knative-serving the jkube resource generation is disabled + serviceLabels.put(BaseTrait.KUBERNETES_LABEL_MANAGED_BY, "camel-jbang"); + serviceLabels.put("app", context.getName()); + serviceLabels.put("app.kubernetes.io/version", context.getVersion()); + serviceLabels.put("provider", "jkube"); + serviceLabels.put("version", context.getVersion()); // Make sure the Eventing webhook will select the source resource, in order to inject the sink information. // This is necessary for Knative environments, that are configured with SINK_BINDING_SELECTION_MODE=inclusion. @@ -120,6 +128,15 @@ public class KnativeServiceTrait extends KnativeBaseTrait { .endTemplate() .endSpec(); + Container containerTrait = Optional.ofNullable(traitConfig.getContainer()).orElseGet(Container::new); + Optional.ofNullable(containerTrait.getImagePullSecrets()).orElseGet(List::of).forEach(sec -> service.editSpec() + .editOrNewTemplate() + .editOrNewSpec() + .addNewImagePullSecret(sec) + .endSpec() + .endTemplate() + .endSpec()); + if (serviceTrait.getTimeoutSeconds() != null && serviceTrait.getTimeoutSeconds() > 0) { service.editSpec() .editTemplate() diff --git a/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/test/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/KubernetesDeleteTest.java b/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/test/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/KubernetesDeleteTest.java index 159ee6307e9..bd92ecfcbb9 100644 --- a/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/test/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/KubernetesDeleteTest.java +++ b/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/test/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/KubernetesDeleteTest.java @@ -134,6 +134,12 @@ class KubernetesDeleteTest { "/apis/apps/v1/namespaces/test/deployments?labelSelector=app%3Dmy-route%2Capp.kubernetes.io%2Fmanaged-by%3Dcamel-jbang"); serverDeleteExpect( "/api/v1/namespaces/test/services?labelSelector=app%3Dmy-route%2Capp.kubernetes.io%2Fmanaged-by%3Dcamel-jbang"); + serverDeleteExpect( + "/apis/serving.knative.dev/v1/namespaces/test/services?labelSelector=app%3Dmy-route%2Capp.kubernetes.io%2Fmanaged-by%3Dcamel-jbang"); + serverDeleteExpect( + "/api/v1/namespaces/test/configmaps?labelSelector=app%3Dmy-route%2Capp.kubernetes.io%2Fmanaged-by%3Dcamel-jbang"); + serverDeleteExpect( + "/api/v1/namespaces/test/secrets?labelSelector=app%3Dmy-route%2Capp.kubernetes.io%2Fmanaged-by%3Dcamel-jbang"); // Execute delete command KubernetesDelete command = new KubernetesDelete(new CamelJBangMain().withPrinter(printer)); @@ -176,6 +182,12 @@ class KubernetesDeleteTest { "/apis/image.openshift.io/v1/namespaces/test/imagestreams?labelSelector=app%3Dmy-route%2Capp.kubernetes.io%2Fmanaged-by%3Dcamel-jbang"); serverDeleteExpect( "/apis/route.openshift.io/v1/namespaces/test/routes?labelSelector=app%3Dmy-route%2Capp.kubernetes.io%2Fmanaged-by%3Dcamel-jbang"); + serverDeleteExpect( + "/apis/serving.knative.dev/v1/namespaces/test/services?labelSelector=app%3Dmy-route%2Capp.kubernetes.io%2Fmanaged-by%3Dcamel-jbang"); + serverDeleteExpect( + "/api/v1/namespaces/test/configmaps?labelSelector=app%3Dmy-route%2Capp.kubernetes.io%2Fmanaged-by%3Dcamel-jbang"); + serverDeleteExpect( + "/api/v1/namespaces/test/secrets?labelSelector=app%3Dmy-route%2Capp.kubernetes.io%2Fmanaged-by%3Dcamel-jbang"); // Execute delete command KubernetesDelete command = new KubernetesDelete(new CamelJBangMain().withPrinter(printer)); diff --git a/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/test/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/KubernetesExportKnativeTest.java b/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/test/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/KubernetesExportKnativeTest.java index c3e345198df..39b798ac3e7 100644 --- a/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/test/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/KubernetesExportKnativeTest.java +++ b/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/test/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/KubernetesExportKnativeTest.java @@ -75,7 +75,7 @@ public class KubernetesExportKnativeTest extends KubernetesExportBaseTest { var labelsB = service.getSpec().getTemplate().getMetadata().getLabels(); Map<String, String> annotations = service.getSpec().getTemplate().getMetadata().getAnnotations(); Assertions.assertEquals("route-service", service.getMetadata().getName()); - Assertions.assertEquals(3, labelsA.size()); + Assertions.assertEquals(8, labelsA.size()); Assertions.assertEquals("route-service", labelsA.get(BaseTrait.KUBERNETES_LABEL_NAME)); Assertions.assertEquals("true", labelsA.get("bindings.knative.dev/include")); Assertions.assertEquals("cluster-local", labelsA.get("networking.knative.dev/visibility")); diff --git a/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/test/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/KubernetesRunCustomTest.java b/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/test/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/KubernetesRunCustomTest.java index ad524b3aa3c..d51d4f2b359 100644 --- a/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/test/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/KubernetesRunCustomTest.java +++ b/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/test/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/KubernetesRunCustomTest.java @@ -29,12 +29,16 @@ import io.fabric8.kubernetes.api.model.Node; import io.fabric8.kubernetes.api.model.NodeBuilder; import io.fabric8.kubernetes.api.model.NodeList; import io.fabric8.kubernetes.api.model.NodeListBuilder; +import io.fabric8.kubernetes.api.model.Service; +import io.fabric8.kubernetes.api.model.apps.Deployment; import io.fabric8.kubernetes.client.KubernetesClient; import io.fabric8.kubernetes.client.server.mock.EnableKubernetesMockClient; import io.fabric8.kubernetes.client.server.mock.KubernetesMockServer; import io.fabric8.openshift.api.model.config.v1.ClusterVersion; import io.fabric8.openshift.api.model.config.v1.ClusterVersionBuilder; +import org.apache.camel.RuntimeCamelException; import org.apache.camel.dsl.jbang.core.commands.CamelJBangMain; +import org.apache.camel.dsl.jbang.core.commands.kubernetes.traits.ContainerTrait; import org.apache.camel.dsl.jbang.core.common.StringPrinter; import org.apache.camel.dsl.jbang.core.common.VersionHelper; import org.junit.jupiter.api.Assertions; @@ -70,19 +74,7 @@ class KubernetesRunCustomTest { @Test public void disableAutomaticClusterDetection() throws Exception { KubernetesHelper.setKubernetesClient(client); - ClusterVersion versionCR = new ClusterVersionBuilder() - .withNewMetadata().withName("version").endMetadata() - .withNewStatus() - .withNewDesired() - .withVersion("4.14.5") - .endDesired() - .endStatus() - .build(); - - server.expect().get().withPath("/apis/config.openshift.io/v1/clusterversions/version") - .andReturn(HttpURLConnection.HTTP_OK, versionCR) - .once(); - + setupServerExpectsOpenshift(); KubernetesRun command = createCommand(new String[] { "classpath:route.yaml" }, "--image-registry=quay.io", "--image-group=camel-test", "--output=yaml", "--disable-auto"); @@ -100,19 +92,7 @@ class KubernetesRunCustomTest { @Test public void detectOpenshiftCluster() throws Exception { KubernetesHelper.setKubernetesClient(client); - ClusterVersion versionCR = new ClusterVersionBuilder() - .withNewMetadata().withName("version").endMetadata() - .withNewStatus() - .withNewDesired() - .withVersion("4.14.5") - .endDesired() - .endStatus() - .build(); - - server.expect().get().withPath("/apis/config.openshift.io/v1/clusterversions/version") - .andReturn(HttpURLConnection.HTTP_OK, versionCR) - .once(); - + setupServerExpectsOpenshift(); KubernetesRun command = createCommand(new String[] { "classpath:route.yaml" }, "--image-registry=quay.io", "--image-group=camel-test", "--output=yaml"); int exit = command.doCall(); @@ -131,19 +111,7 @@ class KubernetesRunCustomTest { @SetEnvironmentVariable(key = "DOCKER_TLS_VERIFY", value = "foo") public void detectMinikubeCluster() throws Exception { KubernetesHelper.setKubernetesClient(client); - Node nodeCR = new NodeBuilder() - .withNewMetadata() - .withName("minikube") - .withLabels(Collections.singletonMap("minikube.k8s.io/name", "minikube")) - .endMetadata() - .build(); - NodeList nodeList = new NodeListBuilder().addToItems(nodeCR) - .build(); - - server.expect().get().withPath("/api/v1/nodes?labelSelector=minikube.k8s.io%2Fname") - .andReturn(HttpURLConnection.HTTP_OK, nodeList) - .once(); - + setupServerExpectsMinikube(); KubernetesRun command = createCommand(new String[] { "classpath:route.yaml" }, "--image-registry=quay.io", "--image-group=camel-test", "--output=yaml"); int exit = command.doCall(); @@ -160,6 +128,102 @@ class KubernetesRunCustomTest { Assertions.assertEquals("docker", command.imageBuilder); } + @Test + @SetEnvironmentVariable(key = "MINIKUBE_ACTIVE_DOCKERD", value = "foo") + @SetEnvironmentVariable(key = "DOCKER_TLS_VERIFY", value = "foo") + public void shouldGenerateKnativeService() throws Exception { + KubernetesHelper.setKubernetesClient(client); + setupServerExpectsMinikube(); + KubernetesRun command = createCommand(new String[] { "classpath:route-service.yaml" }, + "--trait", "knative-service.enabled=true", + "--image-registry=quay.io", "--image-group=camel-test", "--output=yaml"); + int exit = command.doCall(); + + Assertions.assertEquals(0, exit); + Assertions.assertEquals(ClusterType.MINIKUBE.name().toLowerCase(), command.clusterType.toLowerCase()); + + // as the k8s:resource task is skipped for knative-service, there won't be a kubernetes.yml + // so, we add a triple dash to emulate the first line of the kubernetes.yml + String output = "---" + System.lineSeparator() + printer.getOutput(); + var manifest = KubernetesBaseTest.getKubernetesManifestAsStream(output, command.output); + List<HasMetadata> resources = client.load(manifest).items(); + // expects KnativeService only + Assertions.assertEquals(1, resources.size()); + + io.fabric8.knative.serving.v1.Service ksvc = resources.stream() + .filter(it -> io.fabric8.knative.serving.v1.Service.class.isAssignableFrom(it.getClass())) + .map(io.fabric8.knative.serving.v1.Service.class::cast) + .findFirst() + .orElseThrow(() -> new RuntimeCamelException("Missing KnativeService in Kubernetes manifest")); + + var containers = ksvc.getSpec().getTemplate().getSpec().getContainers(); + Assertions.assertEquals(ContainerTrait.KNATIVE_CONTAINER_PORT_NAME, containers.get(0).getPorts().get(0).getName()); + } + + @Test + @SetEnvironmentVariable(key = "MINIKUBE_ACTIVE_DOCKERD", value = "foo") + @SetEnvironmentVariable(key = "DOCKER_TLS_VERIFY", value = "foo") + public void shouldGenerateRegularService() throws Exception { + KubernetesHelper.setKubernetesClient(client); + setupServerExpectsMinikube(); + KubernetesRun command = createCommand(new String[] { "classpath:route-service.yaml" }, + "--image-registry=quay.io", "--image-group=camel-test", "--output=yaml"); + int exit = command.doCall(); + + Assertions.assertEquals(0, exit); + Assertions.assertEquals(ClusterType.MINIKUBE.name().toLowerCase(), command.clusterType.toLowerCase()); + + var manifest = KubernetesBaseTest.getKubernetesManifestAsStream(printer.getOutput(), command.output); + List<HasMetadata> resources = client.load(manifest).items(); + // expects service and deployment only + Assertions.assertEquals(2, resources.size()); + + Service svc = resources.stream() + .filter(it -> Service.class.isAssignableFrom(it.getClass())) + .map(Service.class::cast) + .findFirst() + .orElseThrow(() -> new RuntimeCamelException("Missing Service in Kubernetes manifest")); + + Deployment deployment = resources.stream() + .filter(it -> Deployment.class.isAssignableFrom(it.getClass())) + .map(Deployment.class::cast) + .findFirst() + .orElseThrow(() -> new RuntimeCamelException("Missing deployment in Kubernetes manifest")); + + var containers = deployment.getSpec().getTemplate().getSpec().getContainers(); + Assertions.assertEquals(ContainerTrait.DEFAULT_CONTAINER_PORT_NAME, containers.get(0).getPorts().get(0).getName()); + Assertions.assertEquals(ContainerTrait.DEFAULT_CONTAINER_PORT_NAME, svc.getSpec().getPorts().get(0).getName()); + } + + private void setupServerExpectsMinikube() { + Node nodeCR = new NodeBuilder() + .withNewMetadata() + .withName("minikube") + .withLabels(Collections.singletonMap("minikube.k8s.io/name", "minikube")) + .endMetadata() + .build(); + NodeList nodeList = new NodeListBuilder().addToItems(nodeCR) + .build(); + server.expect().get().withPath("/api/v1/nodes?labelSelector=minikube.k8s.io%2Fname") + .andReturn(HttpURLConnection.HTTP_OK, nodeList) + .once(); + } + + private void setupServerExpectsOpenshift() { + ClusterVersion versionCR = new ClusterVersionBuilder() + .withNewMetadata().withName("version").endMetadata() + .withNewStatus() + .withNewDesired() + .withVersion("4.14.5") + .endDesired() + .endStatus() + .build(); + + server.expect().get().withPath("/apis/config.openshift.io/v1/clusterversions/version") + .andReturn(HttpURLConnection.HTTP_OK, versionCR) + .once(); + } + private KubernetesRun createCommand(String[] files, String... args) { var argsArr = Optional.ofNullable(args).orElse(new String[0]); var argsLst = new ArrayList<>(Arrays.asList(argsArr));