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 d20a5c519deb CAMEL-22644 camel-jbang-kubernetes: add support to 
generate a CronJob instead a Deployment (#20006)
d20a5c519deb is described below

commit d20a5c519deb7f3501dc7f36c59887f6a9763a2b
Author: Claudio Miranda <[email protected]>
AuthorDate: Fri Nov 21 14:01:31 2025 +0000

    CAMEL-22644 camel-jbang-kubernetes: add support to generate a CronJob 
instead a Deployment (#20006)
    
    * camel-jbang export: Fix duplicate duplicate properties starting with 
camel.main
    
    * CAMEL-22644 camel-jbang-kubernetes: add support to generate a CronJob 
instead a Deployment
    https://issues.apache.org/jira/browse/CAMEL-22644
    
    Removed quarkus.management.port from quarkus-kubernetes-pom.tmpl since the 
property is added to application.properties
    Labels moved from container trait to label trait
    docker-java.properties sets api-version  as docker container requires a 
newer docker client version, while camel doesn't update testcontainer
---
 .../modules/ROOT/pages/camel-jbang-kubernetes.adoc |  56 +++++++++
 .../dsl/jbang/core/commands/ExportBaseCommand.java |  12 +-
 .../templates/quarkus-kubernetes-pom.tmpl          |   8 --
 .../camel/dsl/jbang/core/commands/ExportTest.java  |  19 ++-
 .../core/commands/kubernetes/KubernetesExport.java |  64 ++++++-----
 .../core/commands/kubernetes/KubernetesRun.java    |  16 ++-
 .../commands/kubernetes/traits/ContainerTrait.java |   3 -
 .../commands/kubernetes/traits/CronJobTrait.java   | 118 +++++++++++++++++++
 .../kubernetes/traits/DeploymentTrait.java         |   5 +-
 .../commands/kubernetes/traits/LabelTrait.java     |  12 ++
 .../commands/kubernetes/traits/TraitCatalog.java   |   1 +
 .../commands/kubernetes/traits/TraitContext.java   |   7 ++
 .../commands/kubernetes/traits/model/CronJob.java  | 127 +++++++++++++++++++++
 .../commands/kubernetes/traits/model/Traits.java   |  16 ++-
 .../commands/kubernetes/KubernetesExportTest.java  |  63 +++++++++-
 .../commands/kubernetes/KubernetesRunTest.java     |  42 +++++++
 .../src/test/resources/docker-java.properties      |  17 +++
 17 files changed, 527 insertions(+), 59 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 f8197ca903df..af0f7b6b555a 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-jbang-kubernetes.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-jbang-kubernetes.adoc
@@ -329,6 +329,62 @@ spec:
 <3> Custom container name
 <4> Custom container port exposed
 
+=== Cronjob
+
+It's possible to run the workload as a 
https://kubernetes.io/docs/concepts/workloads/controllers/cron-jobs/[CronJob], 
it allows to set some parameters as `schedule`, `timezone`, 
`startingDeadlineSeconds`, `activeDeadlineSeconds`, `backoffLimit`, and 
`durationMaxIdleSeconds`. When the CronJob trait is used, it doesn't contain 
the container health probes, since the Cronjob doesn't use them to start or 
check the pods.
+
+To generate the CronJob manifest, these are required trait parameters: 
`enabled=true` and `schedule`.
+
+.Example
+
+[source,bash]
+----
+camel kubernetes export Sample.java \
+  --trait=cronjob.enabled=true \
+  --trait=cronjob.schedule="* * * * 2" \
+  --trait=cronjob.timezone="Europe/Lisbon" \
+  --trait=cronjob.startingDeadlineSeconds=3 \
+  --trait=cronjob.activeDeadlineSeconds=7 \
+  --trait=cronjob.backoffLimit=4 \
+  --trait=cronjob.durationMaxIdleSeconds=2
+----
+
+The CronJob configuration parameters:
+
+[cols="2m,1m,5a"]
+|===
+|Property | Type | Description
+
+| cronjob.enabled
+| boolean
+| To generate the CronJob manifest instead of Deployment. (default=false)
+
+| cronjob.schedule
+| string
+| To set the schedule to run the workload following the crontab format.
+
+| cronjob.timezone
+| string
+| To set the timezone to run the workload.
+
+| cronjob.startingDeadlineSeconds
+| integer
+| To set the `StartingDeadlineSeconds` field of the CronJob.
+
+| cronjob.activeDeadlineSeconds
+| integer
+| To set the `ActiveDeadlineSeconds` field of the CronJob.
+
+| cronjob.backoffLimit
+| integer
+| To set the `BackoffLimit` field of the CronJob.
+
+| cronjob.durationMaxIdleSeconds
+| integer
+| To set the `camel.main.duration-max-idle-seconds` property for how long time 
in seconds Camel can be idle before automatic terminating the JVM. (default 1)
+
+|===
+
 === Labels and annotations
 
 You may need to add labels or annotations to the generated Kubernetes 
resources.
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ExportBaseCommand.java
 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ExportBaseCommand.java
index 840e8d717adf..0f84ef2cd492 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ExportBaseCommand.java
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ExportBaseCommand.java
@@ -66,6 +66,7 @@ import 
org.apache.camel.tooling.maven.MavenResolutionException;
 import org.apache.camel.util.CamelCaseOrderedProperties;
 import org.apache.camel.util.FileUtil;
 import org.apache.camel.util.IOHelper;
+import org.apache.camel.util.ObjectHelper;
 import org.apache.camel.util.StringHelper;
 import picocli.CommandLine;
 
@@ -821,7 +822,6 @@ public abstract class ExportBaseCommand extends 
CamelCommand {
             customize.apply(profileProps);
         }
 
-        Path appPropsPath = targetDir.resolve("application.properties");
         StringBuilder content = new StringBuilder();
         for (Map.Entry<Object, Object> entry : profileProps.entrySet()) {
             String k = entry.getKey().toString();
@@ -852,17 +852,19 @@ public abstract class ExportBaseCommand extends 
CamelCommand {
 
         // User properties
         Properties userProps = new CamelCaseOrderedProperties();
-        prepareUserProperties(userProps);
+        userProps.putAll(propertiesMap(this.applicationProperties));
         for (Map.Entry<Object, Object> entryUserProp : userProps.entrySet()) {
             String uK = entryUserProp.getKey().toString();
             String uV = entryUserProp.getValue().toString();
             String line = applicationPropertyLine(uK, uV);
-            if (line != null && !line.isBlank()) {
+            // properties from the profile are already included so skip them
+            if (profileProps.get(uK) == null && ObjectHelper.isNotEmpty(line)) 
{
                 content.append(line).append("\n");
             }
         }
 
         // write all the properties
+        Path appPropsPath = targetDir.resolve("application.properties");
         Files.writeString(appPropsPath, content.toString(), 
StandardCharsets.UTF_8);
     }
 
@@ -870,10 +872,6 @@ public abstract class ExportBaseCommand extends 
CamelCommand {
         // NOOP
     }
 
-    protected void prepareUserProperties(Properties properties) {
-        properties.putAll(propertiesMap(this.applicationProperties));
-    }
-
     protected Map<String, String> propertiesMap(String[]... propertySources) {
         Map<String, String> result = new LinkedHashMap<>();
         if (propertySources != null) {
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/resources/templates/quarkus-kubernetes-pom.tmpl
 
b/dsl/camel-jbang/camel-jbang-core/src/main/resources/templates/quarkus-kubernetes-pom.tmpl
index 01b45c5bc3c4..ca543e2b5625 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/main/resources/templates/quarkus-kubernetes-pom.tmpl
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/main/resources/templates/quarkus-kubernetes-pom.tmpl
@@ -18,7 +18,6 @@
         <quarkus.platform.group-id>{{ .QuarkusGroupId 
}}</quarkus.platform.group-id>
         <quarkus.platform.artifact-id>{{ .QuarkusArtifactId 
}}</quarkus.platform.artifact-id>
         <quarkus.platform.version>{{ .QuarkusVersion 
}}</quarkus.platform.version>
-        <quarkus.management.port>{{ .QuarkusManagementPort 
}}</quarkus.management.port>
 {{ .BuildProperties }}
         <skipITs>true</skipITs>
         <surefire-plugin.version>3.5.4</surefire-plugin.version>
@@ -136,13 +135,6 @@
                             </build>
                         </image>
                     </images>
-                    <enricher>
-                        <config>
-                            <jkube-healthcheck-quarkus>
-                                <port>${quarkus.management.port}</port>
-                            </jkube-healthcheck-quarkus>
-                        </config>
-                    </enricher>
                 </configuration>
                 <executions>
                     <execution>
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ExportTest.java
 
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ExportTest.java
index 8bf84d7bfb87..074b0cb5a08b 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ExportTest.java
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ExportTest.java
@@ -26,6 +26,7 @@ import java.nio.file.Paths;
 import java.util.Arrays;
 import java.util.List;
 import java.util.Properties;
+import java.util.Scanner;
 import java.util.stream.Stream;
 
 import org.apache.camel.dsl.jbang.core.common.CamelJBangConstants;
@@ -591,7 +592,8 @@ class ExportTest {
         // We need a real file as we want to test the generated content
         Export command = createCommand(rt, new String[] { 
"src/test/resources/route.yaml" },
                 "--gav=examples:route:1.0.0", "--dir=" + workingDir, "--quiet",
-                "--property", "hello=world");
+                // there was a bug where properties starting with camel.main 
were duplicated in application.properties
+                "--property", "hello=world", "--property", 
"camel.main.foo=bar");
         int exit = command.doCall();
 
         Assertions.assertEquals(0, exit);
@@ -600,10 +602,23 @@ class ExportTest {
 
         // Application properties
         File appProperties = new File(workingDir + "/src/main/resources", 
"application.properties");
-        Assertions.assertTrue(appProperties.exists(), "Missing application 
properties");
+        Assertions.assertTrue(appProperties.exists(), "Missing 
application.properties");
         Properties appProps = new Properties();
         appProps.load(new FileInputStream(appProperties));
         Assertions.assertEquals("world", appProps.getProperty("hello"));
+        Assertions.assertEquals("bar", appProps.getProperty("camel.main.foo"));
+        int nrCamelProps = 0;
+        // read the file as text to read the properties as clean text
+        // as using the Properties class won't allow duplicated properties
+        try (Scanner scanner = new Scanner(new File(workingDir + 
"/src/main/resources/application.properties"))) {
+            while (scanner.hasNextLine()) {
+                String line = scanner.nextLine();
+                if ("camel.main.foo=bar".equals(line)) {
+                    nrCamelProps++;
+                }
+            }
+        }
+        Assertions.assertEquals(1, nrCamelProps, "Duplicated property 
camel.main.foo in application.properties");
     }
 
     @ParameterizedTest
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/KubernetesExport.java
 
b/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/KubernetesExport.java
index 390f6348a9f3..fbd8648526e7 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/KubernetesExport.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/KubernetesExport.java
@@ -378,7 +378,15 @@ public class KubernetesExport extends Export {
         var managementPort = httpManagementPort(settingsPath);
         buildProperties.add("jkube.version=%s".formatted(jkubeVersion));
 
-        setContainerHealthPaths(managementPort);
+        boolean cronJobEnabled = traitsSpec.getCronjob() != null && 
traitsSpec.getCronjob().getEnabled();
+        if (cronJobEnabled) {
+            // set this property to allow the JVM to finish quickly once there 
are no more exchange messages
+            // important for cronjobs so that the jvm can end quickly
+            addToApplicationProperties(
+                    "camel.main.duration-max-idle-seconds=" + 
traitsSpec.getCronjob().getDurationMaxIdleSeconds());
+        } else {
+            setContainerHealthPaths(managementPort);
+        }
 
         // Run export
         int exit = super.doExport();
@@ -515,41 +523,34 @@ public class KubernetesExport extends Export {
             // jkube reads quarkus properties to set the container health 
probes path
             
buildProperties.add("jkube.enricher.jkube-healthcheck-quarkus.port=" + 
probePort);
             
buildProperties.add("quarkus.smallrye-health.root-path=/observe/health");
-            List<String> newProps = new ArrayList<>();
-            newProps.add("quarkus.management.port=" + probePort);
-            if (applicationProperties == null) {
-                applicationProperties = newProps.toArray(new 
String[newProps.size()]);
-            } else {
-                newProps.addAll(Arrays.asList(applicationProperties));
-                applicationProperties = newProps.toArray(new 
String[newProps.size()]);
-            }
+            addToApplicationProperties("quarkus.management.port=" + probePort);
         } else if (RuntimeType.springBoot == runtime) {
-            List<String> newProps = new ArrayList<>();
+            // 
addDependencies("org.springframework.boot:spring-boot-starter-actuator");
             // jkube reads spring-boot properties to set the kubernetes 
container health probes path
             // in this case, jkube reads from the application.properties and 
not from the build properties in pom.xml
-            newProps.add("management.endpoints.web.base-path=/observe");
-            newProps.add("management.server.port=" + probePort);
-            // jkube uses the old property to enable the readiness/liveness 
probes
-            // TODO: rename this property once 
https://github.com/eclipse-jkube/jkube/issues/3690 is fixed
-            newProps.add("management.health.probes.enabled=true");
-            if (applicationProperties == null) {
-                applicationProperties = newProps.toArray(new 
String[newProps.size()]);
-            } else {
-                newProps.addAll(Arrays.asList(applicationProperties));
-                applicationProperties = newProps.toArray(new 
String[newProps.size()]);
-            }
+            
addToApplicationProperties("management.endpoints.web.base-path=/observe",
+                    "management.server.port=" + probePort,
+                    // jkube uses the old property to enable the 
readiness/liveness probes
+                    // TODO: rename this property once 
https://github.com/eclipse-jkube/jkube/issues/3690 is fixed
+                    "management.health.probes.enabled=true");
         } else if (RuntimeType.main == runtime) {
-            List<String> newProps = new ArrayList<>();
-            newProps.add("camel.management.port=" + probePort);
-            if (applicationProperties == null) {
-                applicationProperties = newProps.toArray(new 
String[newProps.size()]);
-            } else {
-                newProps.addAll(Arrays.asList(applicationProperties));
-                applicationProperties = newProps.toArray(new 
String[newProps.size()]);
-            }
+            addToApplicationProperties("camel.management.port=" + probePort);
         }
     }
 
+    // helper method to add parameters to the applicationProperties
+    // it takes care to resize the string array
+    private void addToApplicationProperties(String... lines) {
+        List<String> newProps = new ArrayList<>();
+        for (String line : lines) {
+            newProps.add(line);
+        }
+        if (applicationProperties != null) {
+            newProps.addAll(Arrays.asList(applicationProperties));
+        }
+        applicationProperties = newProps.toArray(new String[newProps.size()]);
+    }
+
     private String extractImageGroup(String image) {
         String[] parts = image.split("/");
         if (parts.length == 3) {
@@ -598,7 +599,6 @@ public class KubernetesExport extends Export {
         if (image != null) {
             return StringHelper.afterLast(image, ":");
         }
-
         return super.getVersion();
     }
 
@@ -606,6 +606,10 @@ public class KubernetesExport extends Export {
         this.applicationProperties = props;
     }
 
+    void setObserve(boolean observe) {
+        this.observe = observe;
+    }
+
     /**
      * Configurer used to customize internal options for the Export command.
      */
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 bfd32b2a3417..9a632cd409cd 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
@@ -291,6 +291,7 @@ public class KubernetesRun extends KubernetesBaseCommand {
 
     private KubernetesPodLogs reusablePodLogs;
     private Printer quietPrinter;
+    private Traits computedTraits;
 
     public KubernetesRun(CamelJBangMain main) {
         this(main, null);
@@ -312,6 +313,7 @@ public class KubernetesRun extends KubernetesBaseCommand {
 
     public Integer doCall() throws Exception {
         String projectName = getProjectName();
+        computedTraits = TraitHelper.parseTraits(traits);
 
         Path baseDir = Path.of(".");
         if (files.size() == 1) {
@@ -348,6 +350,11 @@ public class KubernetesRun extends KubernetesBaseCommand {
 
         String workingDir = getIndexedWorkingDir(projectName);
         KubernetesExport export = configureExport(workingDir, baseDir);
+        boolean cronEnabled = computedTraits.getCronjob() != null && 
computedTraits.getCronjob().getEnabled();
+        if (cronEnabled) {
+            // disable observability-services as CronJob doesn't use the 
container probes
+            export.setObserve(false);
+        }
         int exit = export.export();
         if (exit != 0) {
             printer().printErr("Project export failed!");
@@ -355,8 +362,7 @@ public class KubernetesRun extends KubernetesBaseCommand {
         }
 
         if (output != null) {
-            Traits ptraits = TraitHelper.parseTraits(traits);
-            boolean ksvcEnabled = ptraits.getKnativeService() != null && 
ptraits.getKnativeService().getEnabled();
+            boolean ksvcEnabled = computedTraits.getKnativeService() != null 
&& computedTraits.getKnativeService().getEnabled();
 
             exit = buildProjectOutput(workingDir);
             if (exit != 0) {
@@ -658,8 +664,7 @@ public class KubernetesRun extends KubernetesBaseCommand {
         // suppress maven transfer progress
         args.add("-ntp");
 
-        Traits ptraits = TraitHelper.parseTraits(traits);
-        if (ptraits.getKnativeService() != null && 
ptraits.getKnativeService().getEnabled()) {
+        if (computedTraits.getKnativeService() != null && 
computedTraits.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
@@ -727,8 +732,7 @@ public class KubernetesRun extends KubernetesBaseCommand {
 
         boolean isOpenshift = ClusterType.OPENSHIFT.isEqualTo(clusterType);
         var prefix = isOpenshift ? "oc" : "k8s";
-        Traits ptraits = TraitHelper.parseTraits(traits);
-        if (ptraits.getKnativeService() != null && 
ptraits.getKnativeService().getEnabled()) {
+        if (computedTraits.getKnativeService() != null && 
computedTraits.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
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 03dfe897268d..275722ab8644 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
@@ -111,9 +111,6 @@ public class ContainerTrait extends BaseTrait {
 
         context.doWithCronJobs(j -> j.editOrNewSpec()
                 .editOrNewJobTemplate()
-                .editOrNewMetadata()
-                .addToLabels(KUBERNETES_LABEL_NAME, context.getName())
-                .endMetadata()
                 .editOrNewSpec()
                 .editOrNewTemplate()
                 .editOrNewSpec()
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/traits/CronJobTrait.java
 
b/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/traits/CronJobTrait.java
new file mode 100644
index 000000000000..2ddfc8bd270b
--- /dev/null
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/traits/CronJobTrait.java
@@ -0,0 +1,118 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.camel.dsl.jbang.core.commands.kubernetes.traits;
+
+import java.util.List;
+import java.util.Optional;
+
+import io.fabric8.kubernetes.api.model.batch.v1.CronJobBuilder;
+import 
org.apache.camel.dsl.jbang.core.commands.kubernetes.traits.model.Container;
+import 
org.apache.camel.dsl.jbang.core.commands.kubernetes.traits.model.CronJob;
+import org.apache.camel.dsl.jbang.core.commands.kubernetes.traits.model.Traits;
+import org.apache.camel.util.ObjectHelper;
+
+public class CronJobTrait extends BaseTrait {
+
+    public CronJobTrait() {
+        super("cronjob", 900);
+    }
+
+    @Override
+    public boolean configure(Traits traitConfig, TraitContext context) {
+        return traitConfig.getCronjob() != null
+                && 
Optional.ofNullable(traitConfig.getCronjob().getEnabled()).orElse(false)
+                && 
ObjectHelper.isNotEmpty(traitConfig.getCronjob().getSchedule());
+    }
+
+    @Override
+    public void apply(Traits traitConfig, TraitContext context) {
+        CronJob cronjobTrait = 
Optional.ofNullable(traitConfig.getCronjob()).orElseGet(CronJob::new);
+        CronJobBuilder cronjobBuilder = new CronJobBuilder()
+                .withNewMetadata()
+                .withName(context.getName())
+                .endMetadata()
+                .withNewSpec()
+                .endSpec();
+
+        Container containerTrait = 
Optional.ofNullable(traitConfig.getContainer()).orElseGet(Container::new);
+        // sets the image pull secret
+        
Optional.ofNullable(containerTrait.getImagePullSecrets()).orElseGet(List::of)
+                .forEach(sec -> cronjobBuilder.editOrNewSpec()
+                        .editOrNewJobTemplate()
+                        .editOrNewSpec()
+                        .editOrNewTemplate()
+                        .editOrNewSpec()
+                        .addNewImagePullSecret(sec)
+                        .endSpec()
+                        .endTemplate()
+                        .endSpec()
+                        .endJobTemplate()
+                        .endSpec());
+
+        // sets the service account
+        if (context.getServiceAccount() != null) {
+            cronjobBuilder.editOrNewSpec()
+                    .editOrNewJobTemplate()
+                    .editOrNewSpec()
+                    .editOrNewTemplate()
+                    .editOrNewSpec()
+                    .withServiceAccountName(context.getServiceAccount())
+                    .endSpec()
+                    .endTemplate()
+                    .endSpec()
+                    .endJobTemplate()
+                    .endSpec();
+        }
+
+        // sets the schedule and restart-policy
+        cronjobBuilder.editOrNewSpec()
+                // set the timezone
+                .withSchedule(traitConfig.getCronjob().getSchedule())
+                .editOrNewJobTemplate()
+                .editOrNewSpec()
+                .editOrNewTemplate()
+                .editOrNewSpec()
+                // set the restartPolicy
+                .withRestartPolicy("Never")
+                .endSpec()
+                .endTemplate()
+                .endSpec()
+                .endJobTemplate()
+                .endSpec();
+
+        // sets the timezone
+        if (ObjectHelper.isNotEmpty(cronjobTrait.getTimezone())) {
+            
cronjobBuilder.editOrNewSpec().withTimeZone(cronjobTrait.getTimezone()).endSpec();
+        }
+        // sets the ActiveDeadlineSeconds
+        if (cronjobTrait.getActiveDeadlineSeconds() != null && 
cronjobTrait.getActiveDeadlineSeconds() > 0) {
+            
cronjobBuilder.editOrNewSpec().editOrNewJobTemplate().editOrNewSpec()
+                    
.withActiveDeadlineSeconds(cronjobTrait.getActiveDeadlineSeconds()).endSpec().endJobTemplate().endSpec();
+        }
+        // sets the BackoffLimit
+        if (cronjobTrait.getBackoffLimit() != null && 
cronjobTrait.getBackoffLimit() > 0) {
+            
cronjobBuilder.editOrNewSpec().editOrNewJobTemplate().editOrNewSpec()
+                    
.withBackoffLimit(cronjobTrait.getBackoffLimit()).endSpec().endJobTemplate().endSpec();
+        }
+        // sets the StartingDeadlineSeconds
+        if (cronjobTrait.getStartingDeadlineSeconds() != null && 
cronjobTrait.getStartingDeadlineSeconds() > 0) {
+            
cronjobBuilder.editOrNewSpec().withStartingDeadlineSeconds(cronjobTrait.getStartingDeadlineSeconds()).endSpec();
+        }
+        context.add(cronjobBuilder);
+    }
+}
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 35fa071e68d3..bbafcccb62a0 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,12 +34,15 @@ public class DeploymentTrait extends BaseTrait {
 
     @Override
     public boolean configure(Traits traitConfig, TraitContext context) {
+        // disable the deployment trait if cronjob is enabled
+        boolean cronjobDisabled = traitConfig.getCronjob() == null
+                || 
!Optional.ofNullable(traitConfig.getCronjob().getEnabled()).orElse(false);
         // 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;
+        return cronjobDisabled && !knEnabled;
     }
 
     @Override
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/traits/LabelTrait.java
 
b/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/traits/LabelTrait.java
index 8bebbdf97d6a..8ab08380e953 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/traits/LabelTrait.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/traits/LabelTrait.java
@@ -42,5 +42,17 @@ public class LabelTrait extends BaseTrait {
                         .addToLabels(KUBERNETES_LABEL_NAME, context.getName())
                         .addToLabels(context.getLabels())
                         .endMetadata());
+        context.doWithCronJobs(
+                c -> c.editOrNewMetadata()
+                        .addToLabels(KUBERNETES_LABEL_NAME, context.getName())
+                        .addToLabels(context.getLabels())
+                        .endMetadata()
+                        .editOrNewSpec()
+                        .editOrNewJobTemplate()
+                        .editOrNewMetadata()
+                        .addToLabels(KUBERNETES_LABEL_NAME, context.getName())
+                        .endMetadata()
+                        .endJobTemplate()
+                        .endSpec());
     }
 }
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/traits/TraitCatalog.java
 
b/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/traits/TraitCatalog.java
index 0a491f818aa8..cb67f33a2ce1 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/traits/TraitCatalog.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/traits/TraitCatalog.java
@@ -39,6 +39,7 @@ public class TraitCatalog {
 
     public TraitCatalog() {
         register(new DeploymentTrait());
+        register(new CronJobTrait());
         register(new KnativeTrait());
         register(new KnativeServiceTrait());
         register(new ServiceTrait());
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/traits/TraitContext.java
 
b/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/traits/TraitContext.java
index 866af1b34f5f..84f7fabc48d1 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/traits/TraitContext.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/traits/TraitContext.java
@@ -135,6 +135,13 @@ public class TraitContext {
                 .findFirst();
     }
 
+    public Optional<CronJobBuilder> getCronJob() {
+        return resourceRegistry.stream()
+                .filter(it -> 
it.getClass().isAssignableFrom(CronJobBuilder.class))
+                .map(it -> (CronJobBuilder) it)
+                .findFirst();
+    }
+
     public Optional<IngressBuilder> getIngress() {
         return resourceRegistry.stream()
                 .filter(it -> 
it.getClass().isAssignableFrom(IngressBuilder.class))
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/traits/model/CronJob.java
 
b/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/traits/model/CronJob.java
new file mode 100644
index 000000000000..e91f9a6975d6
--- /dev/null
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/traits/model/CronJob.java
@@ -0,0 +1,127 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.camel.dsl.jbang.core.commands.kubernetes.traits.model;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.annotation.JsonPropertyDescription;
+import com.fasterxml.jackson.annotation.JsonPropertyOrder;
+import com.fasterxml.jackson.annotation.JsonSetter;
+import com.fasterxml.jackson.annotation.Nulls;
+
+@JsonInclude(JsonInclude.Include.NON_NULL)
+@JsonPropertyOrder({
+        "enabled", "schedule", "timezone", "startingDeadlineSeconds", 
"activeDeadlineSeconds", "backoffLimit",
+        "durationMaxIdleSeconds" })
+public class CronJob {
+
+    @JsonProperty("enabled")
+    @JsonPropertyDescription("Can be used to enable or disable a trait.")
+    @JsonSetter(nulls = Nulls.SKIP)
+    private Boolean enabled = Boolean.FALSE;
+
+    @JsonProperty("schedule")
+    @JsonPropertyDescription("The schedule in Cron format.")
+    @JsonSetter(nulls = Nulls.SKIP)
+    private String schedule;
+
+    @JsonProperty("timezone")
+    @JsonPropertyDescription("The time zone name for the given schedule.")
+    @JsonSetter(nulls = Nulls.SKIP)
+    private String timezone;
+
+    @JsonProperty("startingDeadlineSeconds")
+    @JsonPropertyDescription("Optional deadline in seconds for starting the 
job if it misses scheduled time for any reason.")
+    @JsonSetter(nulls = Nulls.SKIP)
+    private Long startingDeadlineSeconds;
+
+    @JsonProperty("activeDeadlineSeconds")
+    @JsonPropertyDescription("Specifies the duration in seconds relative to 
the startTime that the job may be continuously active before the system tries 
to terminate it.")
+    @JsonSetter(nulls = Nulls.SKIP)
+    private Long activeDeadlineSeconds;
+
+    @JsonProperty("backoffLimit")
+    @JsonPropertyDescription("Specifies the number of retries before marking 
this job failed.")
+    @JsonSetter(nulls = Nulls.SKIP)
+    private Integer backoffLimit;
+
+    @JsonProperty("durationMaxIdleSeconds")
+    @JsonPropertyDescription("How long time in seconds Camel can be idle 
before automatic terminating the JVM, it sets the 
camel.main.duration-max-idle-seconds property, default to 1.")
+    @JsonSetter(nulls = Nulls.SKIP)
+    private Integer durationMaxIdleSeconds = 1;
+
+    public CronJob() {
+    }
+
+    public Boolean getEnabled() {
+        return enabled;
+    }
+
+    public void setEnabled(Boolean enabled) {
+        this.enabled = enabled;
+    }
+
+    public String getSchedule() {
+        return schedule;
+    }
+
+    public void setSchedule(String schedule) {
+        this.schedule = schedule;
+    }
+
+    public String getTimezone() {
+        return timezone;
+    }
+
+    public void setTimezone(String timezone) {
+        this.timezone = timezone;
+    }
+
+    public Long getStartingDeadlineSeconds() {
+        return startingDeadlineSeconds;
+    }
+
+    public void setStartingDeadlineSeconds(Long startingDeadlineSeconds) {
+        this.startingDeadlineSeconds = startingDeadlineSeconds;
+    }
+
+    public Long getActiveDeadlineSeconds() {
+        return activeDeadlineSeconds;
+    }
+
+    public void setActiveDeadlineSeconds(Long activeDeadlineSeconds) {
+        this.activeDeadlineSeconds = activeDeadlineSeconds;
+    }
+
+    public Integer getBackoffLimit() {
+        return backoffLimit;
+    }
+
+    public void setBackoffLimit(Integer backoffLimit) {
+        this.backoffLimit = backoffLimit;
+    }
+
+    public Integer getDurationMaxIdleSeconds() {
+        return durationMaxIdleSeconds;
+    }
+
+    public void setDurationMaxIdleSeconds(Integer durationMaxIdleSeconds) {
+        this.durationMaxIdleSeconds = durationMaxIdleSeconds;
+    }
+
+}
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/traits/model/Traits.java
 
b/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/traits/model/Traits.java
index 512f53fe37b7..496663f9c61b 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/traits/model/Traits.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/traits/model/Traits.java
@@ -28,7 +28,8 @@ import com.fasterxml.jackson.annotation.Nulls;
 
 @JsonInclude(JsonInclude.Include.NON_NULL)
 @JsonPropertyOrder({
-        "camel", "container", "environment", "ingress", "knative", 
"knative-service", "mount", "openapi", "pod", "route",
+        "camel", "container", "cronjob", "environment", "ingress", "knative", 
"knative-service", "mount", "openapi", "pod",
+        "route",
         "service", "service-binding", "jolokia" })
 public class Traits {
 
@@ -47,6 +48,11 @@ public class Traits {
     @JsonSetter(nulls = Nulls.SKIP)
     private Container container;
 
+    @JsonProperty("cronjob")
+    @JsonPropertyDescription("The configuration of CronJob trait")
+    @JsonSetter(nulls = Nulls.SKIP)
+    private CronJob cronjob;
+
     @JsonProperty("environment")
     @JsonPropertyDescription("The configuration of Environment trait")
     @JsonSetter(nulls = Nulls.SKIP)
@@ -121,6 +127,14 @@ public class Traits {
         this.container = container;
     }
 
+    public CronJob getCronjob() {
+        return cronjob;
+    }
+
+    public void setCronjob(CronJob cronjob) {
+        this.cronjob = cronjob;
+    }
+
     public Environment getEnvironment() {
         return environment;
     }
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/test/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/KubernetesExportTest.java
 
b/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/test/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/KubernetesExportTest.java
index c5745982518a..99d3729f2bf1 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/test/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/KubernetesExportTest.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/test/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/KubernetesExportTest.java
@@ -27,8 +27,11 @@ import io.fabric8.kubernetes.api.model.ServicePort;
 import io.fabric8.kubernetes.api.model.Volume;
 import io.fabric8.kubernetes.api.model.VolumeMount;
 import io.fabric8.kubernetes.api.model.apps.Deployment;
+import io.fabric8.kubernetes.api.model.batch.v1.CronJob;
+import io.fabric8.kubernetes.api.model.batch.v1.JobSpec;
 import io.fabric8.kubernetes.api.model.networking.v1.Ingress;
 import io.fabric8.openshift.api.model.Route;
+import org.apache.camel.RuntimeCamelException;
 import org.apache.camel.dsl.jbang.core.commands.kubernetes.traits.BaseTrait;
 import org.apache.camel.dsl.jbang.core.common.RuntimeType;
 import org.apache.camel.util.IOHelper;
@@ -76,7 +79,6 @@ class KubernetesExportTest extends KubernetesExportBaseTest {
         Properties applicationProperties = 
getApplicationProperties(workingDir);
 
         if (RuntimeType.quarkus == RuntimeType.fromValue(rt.runtime())) {
-            Assertions.assertEquals("9876", 
props.get("quarkus.management.port"));
             Assertions.assertEquals("9876", 
applicationProperties.get("quarkus.management.port"));
             Assertions.assertEquals("9876", 
props.get("jkube.enricher.jkube-healthcheck-quarkus.port"));
             Assertions.assertEquals("/observe/health", 
props.get("quarkus.smallrye-health.root-path"));
@@ -213,6 +215,65 @@ class KubernetesExportTest extends 
KubernetesExportBaseTest {
         Assertions.assertFalse(hasKnativeService(rt));
     }
 
+    @ParameterizedTest
+    @MethodSource("runtimeProvider")
+    public void shouldGenerateCronjobKubernetesManifest(RuntimeType rt) throws 
Exception {
+        KubernetesExport command = createCommand(new String[] { 
"classpath:route.yaml" },
+                "--image-registry=quay.io", "--image-group=camel-test", 
"--runtime=" + rt.runtime(),
+                "--service-account=my-svc-account");
+        command.traits = new String[] {
+                "cronjob.enabled=true",
+                "cronjob.schedule=\"0 22 * * 1-5\"",
+                "cronjob.timezone=Europe/Lisbon",
+                "cronjob.startingDeadlineSeconds=2",
+                "cronjob.activeDeadlineSeconds=3",
+                "cronjob.backoffLimit=4",
+                "cronjob.durationMaxIdleSeconds=5",
+                "container.imagePullPolicy=Never"
+        };
+        int exit = command.doCall();
+        Assertions.assertEquals(0, exit);
+
+        CronJob cronjob = getResource(rt, CronJob.class)
+                .orElseThrow(() -> new RuntimeCamelException("Cannot find 
cronjob for: %s".formatted(rt.runtime())));
+        JobSpec jobSpec = cronjob.getSpec().getJobTemplate().getSpec();
+        Assertions.assertEquals("route", cronjob.getMetadata().getName());
+        Assertions.assertEquals("0 22 * * 1-5", 
cronjob.getSpec().getSchedule());
+        Assertions.assertEquals("Europe/Lisbon", 
cronjob.getSpec().getTimeZone());
+        Assertions.assertEquals(2, 
cronjob.getSpec().getStartingDeadlineSeconds());
+        Assertions.assertEquals(3, jobSpec.getActiveDeadlineSeconds());
+        Assertions.assertEquals(4, jobSpec.getBackoffLimit());
+        Assertions.assertEquals("Never", 
jobSpec.getTemplate().getSpec().getContainers().get(0).getImagePullPolicy());
+        Assertions.assertEquals("my-svc-account", 
jobSpec.getTemplate().getSpec().getServiceAccountName());
+
+        Properties applicationProperties = 
getApplicationProperties(workingDir);
+        Assertions.assertEquals("5", 
applicationProperties.getProperty("camel.main.duration-max-idle-seconds"));
+
+        Model model = readMavenModel();
+        Assertions.assertEquals("org.example.project", model.getGroupId());
+        Assertions.assertEquals("route", model.getArtifactId());
+        Assertions.assertEquals("1.0-SNAPSHOT", model.getVersion());
+
+        Properties props = model.getProperties();
+        Assertions.assertEquals("quay.io/camel-test/route:1.0-SNAPSHOT", 
props.get("jkube.image.name"));
+        Assertions.assertEquals("quay.io/camel-test/route:1.0-SNAPSHOT", 
props.get("jkube.container-image.name"));
+        Assertions.assertTrue(hasService(rt));
+        Assertions.assertFalse(hasKnativeService(rt));
+
+        // there are no health probes for cronjobs
+        if (RuntimeType.quarkus == RuntimeType.fromValue(rt.runtime())) {
+            
Assertions.assertNull(applicationProperties.get("quarkus.management.port"));
+            
Assertions.assertNull(props.get("jkube.enricher.jkube-healthcheck-quarkus.port"));
+            
Assertions.assertNull(props.get("quarkus.smallrye-health.root-path"));
+        } else if (RuntimeType.springBoot == 
RuntimeType.fromValue(rt.runtime())) {
+            
Assertions.assertNull(applicationProperties.get("management.server.port"));
+            
Assertions.assertNull(applicationProperties.get("management.endpoints.web.base-path"));
+            
Assertions.assertNull(applicationProperties.get("management.health.probes.enabled"));
+        } else if (RuntimeType.main == RuntimeType.fromValue(rt.runtime())) {
+            
Assertions.assertNull(applicationProperties.get("camel.management.port"));
+        }
+    }
+
     @ParameterizedTest
     @MethodSource("runtimeProvider")
     public void shouldAddApplicationProperties(RuntimeType rt) throws 
Exception {
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/test/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/KubernetesRunTest.java
 
b/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/test/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/KubernetesRunTest.java
index b01fb78b6337..d1eb6ed0c05c 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/test/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/KubernetesRunTest.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/test/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/KubernetesRunTest.java
@@ -28,6 +28,8 @@ import java.util.stream.Stream;
 
 import io.fabric8.kubernetes.api.model.HasMetadata;
 import io.fabric8.kubernetes.api.model.apps.Deployment;
+import io.fabric8.kubernetes.api.model.batch.v1.CronJob;
+import io.fabric8.kubernetes.api.model.batch.v1.JobSpec;
 import org.apache.camel.RuntimeCamelException;
 import org.apache.camel.dsl.jbang.core.commands.CamelJBangMain;
 import org.apache.camel.dsl.jbang.core.commands.kubernetes.traits.BaseTrait;
@@ -149,6 +151,46 @@ class KubernetesRunTest extends KubernetesBaseTest {
         }
     }
 
+    @ParameterizedTest
+    @MethodSource("runtimeProvider")
+    public void shouldGenerateKubernetesCronjobManifest(RuntimeType rt) throws 
Exception {
+        KubernetesRun command = createCommand(List.of("classpath:route.yaml"),
+                "--disable-auto=true", "--image-registry=quay.io", 
"--image-group=camel-test", "--output=yaml",
+                "--service-account=my-svc-account", "--runtime=" + 
rt.runtime(), "--java-version=17");
+        command.traits = new String[] {
+                "cronjob.enabled=true",
+                "cronjob.schedule=\"0 22 * * 1-5\"",
+                "cronjob.timezone=Europe/Lisbon",
+                "cronjob.startingDeadlineSeconds=2",
+                "cronjob.activeDeadlineSeconds=3",
+                "cronjob.backoffLimit=4",
+                "cronjob.durationMaxIdleSeconds=5",
+                "container.imagePullPolicy=Never"
+        };
+        int exit = command.doCall();
+        Assertions.assertEquals(0, exit);
+
+        var manifest = getKubernetesManifestAsStream(printer.getOutput(), 
command.output);
+        List<HasMetadata> resources = kubernetesClient.load(manifest).items();
+        Assertions.assertEquals(2, resources.size());
+
+        CronJob cronjob = resources.stream()
+                .filter(it -> CronJob.class.isAssignableFrom(it.getClass()))
+                .map(CronJob.class::cast)
+                .findFirst()
+                .orElseThrow(() -> new RuntimeCamelException("Missing cronjob 
in Kubernetes manifest"));
+
+        JobSpec jobSpec = cronjob.getSpec().getJobTemplate().getSpec();
+        Assertions.assertEquals("route", cronjob.getMetadata().getName());
+        Assertions.assertEquals("0 22 * * 1-5", 
cronjob.getSpec().getSchedule());
+        Assertions.assertEquals("Europe/Lisbon", 
cronjob.getSpec().getTimeZone());
+        Assertions.assertEquals(2, 
cronjob.getSpec().getStartingDeadlineSeconds());
+        Assertions.assertEquals(3, jobSpec.getActiveDeadlineSeconds());
+        Assertions.assertEquals(4, jobSpec.getBackoffLimit());
+        Assertions.assertEquals("Never", 
jobSpec.getTemplate().getSpec().getContainers().get(0).getImagePullPolicy());
+        Assertions.assertEquals("my-svc-account", 
jobSpec.getTemplate().getSpec().getServiceAccountName());
+    }
+
     @ParameterizedTest
     @MethodSource("runtimeProvider")
     public void shouldHandleUnsupportedOutputFormat(RuntimeType rt) throws 
Exception {
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/test/resources/docker-java.properties
 
b/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/test/resources/docker-java.properties
new file mode 100644
index 000000000000..54ee16b0d9cc
--- /dev/null
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/test/resources/docker-java.properties
@@ -0,0 +1,17 @@
+## ---------------------------------------------------------------------------
+## Licensed to the Apache Software Foundation (ASF) under one or more
+## contributor license agreements.  See the NOTICE file distributed with
+## this work for additional information regarding copyright ownership.
+## The ASF licenses this file to You under the Apache License, Version 2.0
+## (the "License"); you may not use this file except in compliance with
+## the License.  You may obtain a copy of the License at
+##
+##      http://www.apache.org/licenses/LICENSE-2.0
+##
+## Unless required by applicable law or agreed to in writing, software
+## distributed under the License is distributed on an "AS IS" BASIS,
+## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+## See the License for the specific language governing permissions and
+## limitations under the License.
+## ---------------------------------------------------------------------------
+api.version=1.44
\ No newline at end of file

Reply via email to