---
title: Create a CronJob Workload with Tanzu Application Platform
tags: ["Kubernetes", "Cartographer", "kind", "Tanzu", "TAP"]
categories: ["Dev", "CaaS", "Kubernetes", "TAP"]
date: 2023-04-18T00:59:32Z
updated: 2023-04-18T01:04:56Z
---

For Tanzu Application Platform (as of 1.4), as stated in the [documentation here](https://docs.vmware.com/en/VMware-Tanzu-Application-Platform/1.4/tap/workloads-workload-types.html) , Out of the Box (OOTB) Supply Chains supports the following Workload Types:

* `type=web` ... intended for scalable web applications. A Knative Service resource is created. Scale to Zero, Zero to N are supported.
* `type=server` ... intended for traditional web applications. K8s standard Deployment and Service resources are created.
* `type=worker` ... intended for background applications processing queues. A K8s standard Deployment is created.


This time, add `type=cronjob` here so that K8s standard CronJob resource can be created.


TAP's workload is managed by [Cartographer](https://cartographer.sh/).
Customizing the resources created by Workload is typically done by adding a ClusterSupplyChain resource.
However, when creating a Supply Chain from scratch, it is necessary to define the components that make up the Supply Chain like bellow

* Retrieve the source code
* Scan the vulnerability of the source code vulnerability
* Test the source code
* Build the container image
* Scan the vulnerability of the container image
* Create manifests
* Package and push the manifests
* etc ... (Tutorial [here](https://cartographer.sh/docs/v0.7.0/tutorials/first-supply-chain/))

Redefining what is defined in TAP's OOTB Supply Chain is a pain.

TAP allows you to customize only the "Create manifests" part in the OOTB Supply Chain.
Documentation [here](https://docs.vmware.com/en/VMware-Tanzu-Application-Platform/1.4/tap/workloads-server.html#define-a-workload-type-that-exposes-server-workloads-outside-the-cluster-5).
The documentation gives an example of creating an Ingress resource in addition to a resource of `type=server`.
You can define the manifest template you want the supply chain to generate in Cartographer's ClusterConfigTemplate.

Try it in the environment created in [this article](https://ik.am/entries/733).

**table of contents**
<!-- toc -->

### Defining a ClusterConfigTemplate

Define CronJob's template in ClusterConfigTemplate as follows:

```yaml
cat <<EOF > cronjob-template.yaml
apiVersion: carto.run/v1alpha1
kind: ClusterConfigTemplate
metadata:
  name: cronjob-template
spec:
  configPath: .data
  lifecycle: mutable
  ytt: |
    #@ load("@ytt:data", "data")
    #@ load("@ytt:yaml", "yaml")
    
    #@ def merge_labels(fixed_values):
    #@   labels = {}
    #@   if hasattr(data.values.workload.metadata, "labels"):
    #@     labels.update(data.values.workload.metadata.labels)
    #@   end
    #@   labels.update(fixed_values)
    #@   return labels
    #@ end

    #@ def update_config(config):
    #@   values = {}
    #@   values.update(config)
    #@   spec = dict(values["spec"])
    #@   spec.update({"restartPolicy": data.values.params.restartPolicy if hasattr(data.values.params, "restartPolicy") else "Never"})
    #@   workload = dict(spec["containers"][0])
    #@   if hasattr(data.values.params, "command"):
    #@     workload.update({"command": data.values.params.command})
    #@   end
    #@   if hasattr(data.values.params, "args"):
    #@     workload.update({"args": data.values.params.args})
    #@   end
    #@   spec["containers"][0] = workload
    #@   values["spec"] = spec
    #@   return values
    #@ end
    
    #@ def delivery():
    apiVersion: batch/v1
    kind: CronJob
    metadata:
      name: #@ data.values.workload.metadata.name
      annotations:
        kapp.k14s.io/update-strategy: "fallback-on-replace"
        ootb.apps.tanzu.vmware.com/servicebinding-workload: "true"
        kapp.k14s.io/change-rule: "upsert after upserting servicebinding.io/ServiceBindings"
      labels: #@ merge_labels({ "app.kubernetes.io/component": "run", "carto.run/workload-name": data.values.workload.metadata.name })
    spec:
      schedule: #@ data.values.params.schedule if hasattr(data.values.params, "schedule") else "0 15 * * *"
      successfulJobsHistoryLimit: 1
      failedJobsHistoryLimit: 3
      concurrencyPolicy: Forbid
      jobTemplate:
        metadata:
          labels: #@ data.values.config.metadata.labels
        spec:
          backoffLimit: 0
          template: #@ update_config(data.values.config)
    #@ end
    
    ---
    apiVersion: v1
    kind: ConfigMap
    metadata:
      name: #@ data.values.workload.metadata.name + "-cronjob"
      labels: #@ merge_labels({ "app.kubernetes.io/component": "config" })
    data:
      delivery.yml: #@ yaml.encode(delivery())
EOF
```

> Instead of K8s standard CronJob, If you want to use an extended scheduler like [Argo Workflows](https://argoproj.github.io/argo-workflows/cron-workflows//sample-configuration) or [Furiko](https://furiko.io/docs/execution/jobconfig), you can also change this ClusterConfigTemplate.

This CronJob manifest will eventually be deployed by the Deliverable, so the Service Account that manages the Deliverable must have permission to create the CronJob.
Labeling the ClusterRole `apps.tanzu.vmware.com/aggregate-to-deliverable: "true"` binds it to the Service Account that manages the Deliverable.


```yaml
cat <<EOF > deliverable-with-cronjob.yml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: deliverable-with-cronjob
  labels:
    apps.tanzu.vmware.com/aggregate-to-deliverable: "true"
rules:
- apiGroups:
  - batch
  resources:
  - cronjobs
  verbs:
  - get
  - list
  - watch
  - create
  - patch
  - update
  - delete
  - deletecollection
EOF
```

apply these.

```
# In case of Muti Cluster configuration, in Build Cluster
kubectl apply -f cronjob-template.yaml
```
```
# In case of Muti Cluster configuration, in Run Cluster
kubectl apply -f deliverable-with-cronjob.yml
```

Finally, add a setting to add Workload Type to `tap-values.yaml`.

```yaml
ootb_supply_chain_basic: # if supply_chain value is basic
# or ootb_supply_chain_testing: if supply_chain value is testing
# or ootb_supply_chain_testing_scanning: if supply_chain value is testing_scanning
  supported_workloads:
  - type: web
    cluster_config_template_name: config-template
  - type: server
    cluster_config_template_name: server-template
  - type: worker
    cluster_config_template_name: worker-template
  - type: cronjob
    cluster_config_template_name: cronjob-template # <---
```

Reflect changes in `tap-values.yaml`.

```
tanzu package installed update -n tap-install tap --values-file tap-values.yaml
```

Once the update is complete, you can see that the source-to-url Supply Chain has been added with `type=cronjob`.

```
$ tanzu apps cluster-supply-chain get source-to-url 
---
# source-to-url: Ready
---
Supply Chain Selectors
   TYPE          KEY                                   OPERATOR   VALUE
   expressions   apps.tanzu.vmware.com/workload-type   In         web
   expressions   apps.tanzu.vmware.com/workload-type   In         server
   expressions   apps.tanzu.vmware.com/workload-type   In         worker
   expressions   apps.tanzu.vmware.com/workload-type   In         cronjob
```

Currently, the Service Binding function cannot be used. Bind to CronJob is not supported.


### (Optional) Change settings with Overlays

You may not like the procedure of updating the TAP by running the bellow commands after installing TAP as it is not declarative installation of TAP.

```
kubectl apply -f cronjob-template.yaml
kubectl apply -f deliverable-with-cronjob.yml
```


ClusterConfigTemplate and ClusterRole can be created at the same time when installing/updating TAP with `tanzu package install` or `tanzu package installed update`.


In this case, create a ClusterConfigTemplate and a ClusterRole as overlays as follows:

```
# In case of Muti Cluster configuration, in Build Cluster
kubectl -n tap-install create secret generic ootb-templates-cronjob-template \
  -o yaml \
  --dry-run=client \
  --from-file=cronjob-template.yaml \
  | kubectl apply -f-
```

```
# In case of Muti Cluster configuration, in Run Cluster
kubectl -n tap-install create secret generic tap-auth-deliverable-with-cronjob \
  -o yaml \
  --dry-run=client \
  --from-file=deliverable-with-cronjob.yml \
  | kubectl apply -f-
```

Add the following settings to `tap-values.yaml`.

```yaml
package_overlays:
- name: ootb-templates
  secrets:
  - name: ootb-templates-cronjob-template # in Build Cluster for Muti Cluster configuration
- name: tap-auth
  secrets:
  - name: tap-auth-deliverable-with-cronjob # in Run Cluster for Muti Cluster configuration
```

For first-time installation,

```
tanzu package install tap -p tap.tanzu.vmware.com -v 1.4.2 --values-file tap-values.yaml -n tap-install
```

For update

```
tanzu package installed update -n tap-install tap --values-file tap-values.yaml
```

Now you can use `type=cronjob`.

### Creating CronJob Workloads

Let's create various CronJobs.

#### A Simple CronJob

Let's create a sample from [Kubernetes official documentation](https://kubernetes.io/docs/concepts/workloads/controllers/cron-jobs/).

Create a Workload to create a CronJob with the following command. This time, `--image` specifies the container image to be run by CronJob.

```
tanzu apps workload apply \
  -n demo \
  --type cronjob \
  --image ghcr.io/making/busybox \
  --param-yaml 'command=["/bin/sh", "-c", "date; echo Hello from TAP"]' \
  --param schedule="* * * * *" \
  --app cronjob-demo \
  cronjob-demo
```


> As in the official document, when running with `--image busybox:1.28`, the following error occurred in the ImageRepository resource.
> `unable to pull image "busybox:1.28": Expected an Image but got: application/vnd.docker.distribution.manifest.list.v2+json`
> To fix the problem, I did a relocation like this:
> ```
> docker pull busybox
> docker tag busybox ghcr.io/making/busybox
> docker push busybox
> ```

After a while a CronJob will be created and more Jobs will be executed.

```
$ tanzu apps workload get cronjob-demo --namespace demo
📡 Overview
   name:        cronjob-demo
   type:        cronjob
   namespace:   demo

💾 Source
   type:    image
   image:   ghcr.io/making/busybox

📦 Supply Chain
   name:   basic-image-to-url

   NAME               READY   HEALTHY   UPDATED   RESOURCE
   image-provider     True    True      10m       imagerepositories.source.apps.tanzu.vmware.com/cronjob-demo
   config-provider    True    True      10m       podintents.conventions.carto.run/cronjob-demo
   app-config         True    True      10m       configmaps/cronjob-demo-cronjob
   service-bindings   True    True      10m       configmaps/cronjob-demo-with-claims
   api-descriptors    True    True      10m       configmaps/cronjob-demo-with-api-descriptors
   config-writer      True    True      10m       runnables.carto.run/cronjob-demo-config-writer

🚚 Delivery
   name:   delivery-basic

   NAME              READY   HEALTHY   UPDATED   RESOURCE
   source-provider   True    True      9m34s     imagerepositories.source.apps.tanzu.vmware.com/cronjob-demo-delivery
   deployer          True    True      60s       apps.kappctrl.k14s.io/cronjob-demo

💬 Messages
   No messages found.

🛶 Pods
   NAME                                   READY   STATUS      RESTARTS   AGE
   cronjob-demo-28018560-zdp85            0/1     Completed   0          4s
   cronjob-demo-config-writer-l68rw-pod   0/1     Completed   0          10m

To see logs: "tanzu apps workload tail cronjob-demo --namespace demo --timestamp --since 1h"
```

Check the created CronJobs and Jobs.


```
$ kubectl get cronjob,job -n demo 
NAME                         SCHEDULE    SUSPEND   ACTIVE   LAST SCHEDULE   AGE
cronjob.batch/cronjob-demo   * * * * *   False     0        23s             81s

NAME                              COMPLETIONS   DURATION   AGE
job.batch/cronjob-demo-28018560   1/1           7s         23s
```

Check the logs. time is displayed.

```
$ kubectl logs -n demo cronjob-demo-28018560-zdp85 
Mon Apr 10 08:00:04 UTC 2023
Hello from TAP
```

Since the `schedule` is `* * * * *`, this Job will run every minute.

After confirming, delete the workload.

```
tanzu apps workload delete -n demo cronjob-demo
```

#### A CronJob that just runs curl

Create a Workload to create a CronJob with the following command.

```
tanzu apps workload apply \
  -n demo \
  --type cronjob \
  --image ghcr.io/making/curl \
  --param-yaml 'command=["curl"]' \
  --param-yaml 'args=["-s", "https://httpbin.org/get"]' \
  --param schedule="* * * * *" \
  --app hello-curl \
  hello-curl
```

After a while a CronJob will be created and more Jobs will be executed.

```
$ tanzu apps workload get hello-curl --namespace demo
📡 Overview
   name:        hello-curl
   type:        cronjob
   namespace:   demo

💾 Source
   type:    image
   image:   ghcr.io/making/curl

📦 Supply Chain
   name:   basic-image-to-url

   NAME               READY   HEALTHY   UPDATED   RESOURCE
   image-provider     True    True      106s      imagerepositories.source.apps.tanzu.vmware.com/hello-curl
   config-provider    True    True      102s      podintents.conventions.carto.run/hello-curl
   app-config         True    True      102s      configmaps/hello-curl-cronjob
   service-bindings   True    True      102s      configmaps/hello-curl-with-claims
   api-descriptors    True    True      102s      configmaps/hello-curl-with-api-descriptors
   config-writer      True    True      91s       runnables.carto.run/hello-curl-config-writer

🚚 Delivery
   name:   delivery-basic

   NAME              READY   HEALTHY   UPDATED   RESOURCE
   source-provider   True    True      44s       imagerepositories.source.apps.tanzu.vmware.com/hello-curl-delivery
   deployer          True    True      42s       apps.kappctrl.k14s.io/hello-curl

💬 Messages
   No messages found.

🛶 Pods
   NAME                                 READY   STATUS      RESTARTS   AGE
   hello-curl-28018564-2rlgc            0/1     Completed   0          11s
   hello-curl-config-writer-k76bd-pod   0/1     Completed   0          101s

To see logs: "tanzu apps workload tail hello-curl --namespace demo --timestamp --since 1h"
```

Check the created CronJobs and Jobs.

```
$ kubectl get cronjob,job -n demo 
NAME                       SCHEDULE    SUSPEND   ACTIVE   LAST SCHEDULE   AGE
cronjob.batch/hello-curl   * * * * *   False     0        42s             76s

NAME                            COMPLETIONS   DURATION   AGE
job.batch/hello-curl-28018564   1/1           8s         42s
```

Check the logs. The execution result of curl is output.

```
$ kubectl logs -n demo hello-curl-28018564-2rlgc 
{
  "args": {}, 
  "headers": {
    "Accept": "*/*", 
    "Host": "httpbin.org", 
    "User-Agent": "curl/8.0.1-DEV", 
    "X-Amzn-Trace-Id": "Root=1-6433c2f5-5f649b191de9f1ea4e83fb8f"
  }, 
  "origin": "220.242.176.124", 
  "url": "https://httpbin.org/get"
}
```

After confirming, delete the workload.

```
tanzu apps workload delete -n demo hello-curl
```

#### CronJob to run Python Script

Next, create a container image to be run by CronJob from the source code.
Convert the Python code from https://github.com/making/hello-python into a container image with Cloud Native Buildpacks and run CronJob.


Create a Workload to create a CronJob with the following command. This time, specify the URL of the source code with `--git-repo`.

```
tanzu apps workload apply \
   -n demo \
   --type cronjob \
   --git-repo https://github.com/making/hello-python\
   --git-branch main\
   --param schedule="* * * * *" \
   --app hello-python \
   --label apps.tanzu.vmware.com/has-tests=true \
   hello-python
```

After a while a CronJob will be created and more Jobs will be executed.

```
$ tanzu apps workload get hello-python --namespace demo 
📡 Overview
   name:        hello-python
   type:        cronjob
   namespace:   demo

💾 Source
   type:     git
   url:      https://github.com/making/hello-python
   branch:   main

📦 Supply Chain
   name:   source-to-url

   NAME               READY   HEALTHY   UPDATED   RESOURCE
   source-provider    True    True      4m58s     gitrepositories.source.toolkit.fluxcd.io/hello-python
   image-provider     True    True      3m58s     images.kpack.io/hello-python
   config-provider    True    True      3m52s     podintents.conventions.carto.run/hello-python
   app-config         True    True      3m52s     configmaps/hello-python-cronjob
   service-bindings   True    True      3m52s     configmaps/hello-python-with-claims
   api-descriptors    True    True      3m52s     configmaps/hello-python-with-api-descriptors
   config-writer      True    True      3m38s     runnables.carto.run/hello-python-config-writer

🚚 Delivery
   name:   delivery-basic

   NAME              READY   HEALTHY   UPDATED   RESOURCE
   source-provider   True    True      2m54s     imagerepositories.source.apps.tanzu.vmware.com/hello-python-delivery
   deployer          True    True      2m52s     apps.kappctrl.k14s.io/hello-python

💬 Messages
   No messages found.

🛶 Pods
   NAME                                   READY   STATUS      RESTARTS   AGE
   hello-python-28018837-25db8            0/1     Completed   0          32s
   hello-python-build-1-build-pod         0/1     Completed   0          4m57s
   hello-python-config-writer-ch8z4-pod   0/1     Completed   0          3m50s
```

Check the created CronJobs and Jobs.

```
$ kubectl get cronjob,job -n demo 
NAME                         SCHEDULE    SUSPEND   ACTIVE   LAST SCHEDULE   AGE
cronjob.batch/hello-python   * * * * *   False     0        45s             3m6s

NAME                              COMPLETIONS   DURATION   AGE
job.batch/hello-python-28018837   1/1           5s         45s
```

Check the logs.

```
$ kubectl logs -n demo hello-python-28018837-25db8 
{
  "args": {}, 
  "headers": {
    "Accept": "*/*", 
    "Accept-Encoding": "gzip, deflate", 
    "Host": "httpbin.org", 
    "User-Agent": "python-requests/2.28.2", 
    "X-Amzn-Trace-Id": "Root=1-643402ed-0e6ea59220b36d3636bd2a41"
  }, 
  "origin": "220.242.176.124", 
  "url": "https://httpbin.org/get"
}
```

After confirming, delete the workload.


```
tanzu apps workload delete -n demo hello-python
```

#### CronJob to run Spring Batch Job

Next, the batch processing source code https://github.com/making/billing-job implemented by Spring Batch will be converted into a container image with Cloud Native Buildpacks and executed by CronJob.

Create a Workload to create a CronJob with the following command.


```
tanzu apps workload apply \
  -n demo \
  --type cronjob \
  --git-repo https://github.com/making/billing-job \
  --git-branch master \
  --param schedule="0 15 * * *" \
  --app billing-job \
  --label apps.tanzu.vmware.com/has-tests=true \
  --build-env BP_JVM_VERSION=17 \
  --env BPL_JVM_THREAD_COUNT=8 \
  --env JAVA_TOOL_OPTIONS=-Dmanagement.health.probes.enabled=false \
  billing-job
```


> `--env JAVA_TOOL_OPTIONS=-Dmanagement.health.probes.enabled=false` is added to disable the probe settings that are automatically added when using Spring Boot Actuator. <br>
> Documentation [here](https://docs.vmware.com/en/VMware-Tanzu-Application-Platform/1.4/tap/spring-boot-conventions-reference-CONVENTIONS.html#spring-boot-actuator-probes-convention-5)

A CronJob will be created after a while. Since we have scheduled it to run at 00:00 JST, the Job has not yet been created.

```
 $ tanzu apps workload get billing-job --namespace demo
📡 Overview
   name:        billing-job
   type:        cronjob
   namespace:   demo

💾 Source
   type:     git
   url:      https://github.com/making/billing-job
   branch:   master

📦 Supply Chain
   name:   source-to-url

   NAME               READY   HEALTHY   UPDATED   RESOURCE
   source-provider    True    True      4m51s     gitrepositories.source.toolkit.fluxcd.io/billing-job
   image-provider     True    True      3m39s     images.kpack.io/billing-job
   config-provider    True    True      3m34s     podintents.conventions.carto.run/billing-job
   app-config         True    True      3m34s     configmaps/billing-job-cronjob
   service-bindings   True    True      3m33s     configmaps/billing-job-with-claims
   api-descriptors    True    True      3m33s     configmaps/billing-job-with-api-descriptors
   config-writer      True    True      3m17s     runnables.carto.run/billing-job-config-writer

🚚 Delivery
   name:   delivery-basic

   NAME              READY   HEALTHY   UPDATED   RESOURCE
   source-provider   True    True      2m48s     imagerepositories.source.apps.tanzu.vmware.com/billing-job-delivery
   deployer          True    True      2m46s     apps.kappctrl.k14s.io/billing-job

💬 Messages
   No messages found.

🛶 Pods
   NAME                                  READY   STATUS      RESTARTS   AGE
   billing-job-build-1-build-pod         0/1     Completed   0          4m52s
   billing-job-config-writer-mlhff-pod   0/1     Completed   0          3m33s

To see logs: "tanzu apps workload tail billing-job --namespace demo --timestamp --since 1h"
```

Check the created CronJob.

```
$ kubectl get cronjob,job -n demo 
NAME                        SCHEDULE     SUSPEND   ACTIVE   LAST SCHEDULE   AGE
cronjob.batch/billing-job   0 15 * * *   False     0        <none>          3m48s
```

Create a Job manually.

```
kubectl create job -n demo --from cronjob/billing-job test-run
```

Check the created CronJobs and Jobs.

```
$ kubectl get cronjob,job -n demo 
NAME                        SCHEDULE     SUSPEND   ACTIVE   LAST SCHEDULE   AGE
cronjob.batch/billing-job   0 15 * * *   False     0        <none>          5m12s

NAME                 COMPLETIONS   DURATION   AGE
job.batch/test-run   1/1           13s        26s
```

Check the logs.

```
$ kubectl logs -n demo test-run-c9r5d 
Setting Active Processor Count to 8
Calculating JVM memory based on 4528180K available memory
For more information on this calculation, see https://paketo.io/docs/reference/java-reference/#memory-calculator
Calculated JVM Memory Configuration: -XX:MaxDirectMemorySize=10M -Xmx4191438K -XX:MaxMetaspaceSize=72549K -XX:ReservedCodeCacheSize=240M -Xss1M (Total Memory: 4528180K, Thread Count: 8, Loaded Class Count: 10395, Headroom: 0%)
Enabling Java Native Memory Tracking
Adding 124 container CA certificates to JVM truststore
Spring Cloud Bindings Enabled
Picked up JAVA_TOOL_OPTIONS: -Dmanagement.health.probes.enabled="false" -Djava.security.properties=/layers/paketo-buildpacks_bellsoft-liberica/java-security-properties/java-security.properties -XX:+ExitOnOutOfMemoryError -XX:ActiveProcessorCount=8 -XX:MaxDirectMemorySize=10M -Xmx4191438K -XX:MaxMetaspaceSize=72549K -XX:ReservedCodeCacheSize=240M -Xss1M -XX:+UnlockDiagnosticVMOptions -XX:NativeMemoryTracking=summary -XX:+PrintNMTStatistics -Dorg.springframework.cloud.bindings.boot.enable=true

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::                (v3.0.5)

2023-04-10T16:38:45.371Z  INFO 1 --- [           main] lol.maki.billing.BillingJobApplication   : Starting BillingJobApplication v0.0.1-SNAPSHOT using Java 17.0.6 with PID 1 (/workspace/BOOT-INF/classes started by cnb in /workspace)
2023-04-10T16:38:45.376Z  INFO 1 --- [           main] lol.maki.billing.BillingJobApplication   : No active profile set, falling back to 1 default profile: "default"
2023-04-10T16:38:46.207Z  INFO 1 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Starting...
2023-04-10T16:38:46.436Z  INFO 1 --- [           main] com.zaxxer.hikari.pool.HikariPool        : HikariPool-1 - Added connection conn0: url=jdbc:h2:mem:b918459b-19ff-41d4-8f50-d482bba0dee8 user=SA
2023-04-10T16:38:46.440Z  INFO 1 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Start completed.
2023-04-10T16:38:46.829Z  INFO 1 --- [           main] lol.maki.billing.BillingJobApplication   : Started BillingJobApplication in 1.968 seconds (process running for 2.419)
2023-04-10T16:38:46.832Z  INFO 1 --- [           main] o.s.b.a.b.JobLauncherApplicationRunner   : Running default command line with: []
2023-04-10T16:38:46.844Z DEBUG 1 --- [           main] o.s.jdbc.core.JdbcTemplate               : Executing prepared SQL query
2023-04-10T16:38:46.845Z DEBUG 1 --- [           main] o.s.jdbc.core.JdbcTemplate               : Executing prepared SQL statement [SELECT JOB_INSTANCE_ID, JOB_NAME from BATCH_JOB_INSTANCE where JOB_NAME = ? and JOB_KEY = ?]
2023-04-10T16:38:46.860Z DEBUG 1 --- [           main] o.s.jdbc.core.JdbcTemplate               : Executing prepared SQL query
2023-04-10T16:38:46.860Z DEBUG 1 --- [           main] o.s.jdbc.core.JdbcTemplate               : Executing prepared SQL statement [SELECT JOB_INSTANCE_ID, JOB_NAME from BATCH_JOB_INSTANCE I1 where I1.JOB_NAME = ? and I1.JOB_INSTANCE_ID in (SELECT max(I2.JOB_INSTANCE_ID) from BATCH_JOB_INSTANCE I2 where I2.JOB_NAME = ?)]
2023-04-10T16:38:46.868Z DEBUG 1 --- [           main] o.s.jdbc.core.JdbcTemplate               : Executing prepared SQL query
2023-04-10T16:38:46.869Z DEBUG 1 --- [           main] o.s.jdbc.core.JdbcTemplate               : Executing prepared SQL statement [SELECT JOB_INSTANCE_ID, JOB_NAME from BATCH_JOB_INSTANCE where JOB_NAME = ? and JOB_KEY = ?]
2023-04-10T16:38:46.871Z DEBUG 1 --- [           main] o.s.jdbc.core.JdbcTemplate               : Executing prepared SQL query
2023-04-10T16:38:46.871Z DEBUG 1 --- [           main] o.s.jdbc.core.JdbcTemplate               : Executing prepared SQL statement [SELECT JOB_INSTANCE_ID, JOB_NAME from BATCH_JOB_INSTANCE where JOB_NAME = ? and JOB_KEY = ?]
2023-04-10T16:38:46.872Z DEBUG 1 --- [           main] o.s.jdbc.core.JdbcTemplate               : Executing prepared SQL query
2023-04-10T16:38:46.872Z DEBUG 1 --- [           main] o.s.jdbc.core.JdbcTemplate               : Executing prepared SQL statement [SELECT JOB_INSTANCE_ID, JOB_NAME from BATCH_JOB_INSTANCE where JOB_NAME = ? and JOB_KEY = ?]
2023-04-10T16:38:46.878Z DEBUG 1 --- [           main] o.s.jdbc.core.JdbcTemplate               : Executing prepared SQL update
2023-04-10T16:38:46.879Z DEBUG 1 --- [           main] o.s.jdbc.core.JdbcTemplate               : Executing prepared SQL statement [INSERT into BATCH_JOB_INSTANCE(JOB_INSTANCE_ID, JOB_NAME, JOB_KEY, VERSION) values (?, ?, ?, ?)]
2023-04-10T16:38:46.889Z DEBUG 1 --- [           main] o.s.jdbc.core.JdbcTemplate               : Executing prepared SQL update
2023-04-10T16:38:46.889Z DEBUG 1 --- [           main] o.s.jdbc.core.JdbcTemplate               : Executing prepared SQL statement [INSERT into BATCH_JOB_EXECUTION(JOB_EXECUTION_ID, JOB_INSTANCE_ID, START_TIME, END_TIME, STATUS, EXIT_CODE, EXIT_MESSAGE, VERSION, CREATE_TIME, LAST_UPDATED) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)]
2023-04-10T16:38:46.892Z DEBUG 1 --- [           main] o.s.jdbc.core.JdbcTemplate               : Executing SQL batch update [INSERT into BATCH_JOB_EXECUTION_PARAMS(JOB_EXECUTION_ID, PARAMETER_NAME, PARAMETER_TYPE, PARAMETER_VALUE, IDENTIFYING) values (?, ?, ?, ?, ?)] with a batch size of 100
2023-04-10T16:38:46.893Z DEBUG 1 --- [           main] o.s.jdbc.core.JdbcTemplate               : Executing prepared SQL statement [INSERT into BATCH_JOB_EXECUTION_PARAMS(JOB_EXECUTION_ID, PARAMETER_NAME, PARAMETER_TYPE, PARAMETER_VALUE, IDENTIFYING) values (?, ?, ?, ?, ?)]
2023-04-10T16:38:46.901Z DEBUG 1 --- [           main] o.s.jdbc.core.JdbcTemplate               : Executing prepared SQL update
2023-04-10T16:38:46.902Z DEBUG 1 --- [           main] o.s.jdbc.core.JdbcTemplate               : Executing prepared SQL statement [INSERT INTO BATCH_JOB_EXECUTION_CONTEXT (SHORT_CONTEXT, SERIALIZED_CONTEXT, JOB_EXECUTION_ID) VALUES(?, ?, ?)]
2023-04-10T16:38:46.904Z  INFO 1 --- [           main] o.s.b.c.l.support.SimpleJobLauncher      : Job: [SimpleJob: [name=BillingJob]] launched with the following parameters: [{'run.id':'{value=1, type=class java.lang.Long, identifying=true}'}]
2023-04-10T16:38:46.922Z DEBUG 1 --- [           main] o.s.jdbc.core.JdbcTemplate               : Executing prepared SQL query
2023-04-10T16:38:46.923Z DEBUG 1 --- [           main] o.s.jdbc.core.JdbcTemplate               : Executing prepared SQL statement [SELECT VERSION FROM BATCH_JOB_EXECUTION WHERE JOB_EXECUTION_ID=?]
2023-04-10T16:38:46.928Z DEBUG 1 --- [           main] o.s.jdbc.core.JdbcTemplate               : Executing prepared SQL query
2023-04-10T16:38:46.928Z DEBUG 1 --- [           main] o.s.jdbc.core.JdbcTemplate               : Executing prepared SQL statement [SELECT COUNT(*) FROM BATCH_JOB_EXECUTION WHERE JOB_EXECUTION_ID = ?]
2023-04-10T16:38:46.932Z DEBUG 1 --- [           main] o.s.jdbc.core.JdbcTemplate               : Executing prepared SQL update
2023-04-10T16:38:46.932Z DEBUG 1 --- [           main] o.s.jdbc.core.JdbcTemplate               : Executing prepared SQL statement [UPDATE BATCH_JOB_EXECUTION set START_TIME = ?, END_TIME = ?,  STATUS = ?, EXIT_CODE = ?, EXIT_MESSAGE = ?, VERSION = ?, CREATE_TIME = ?, LAST_UPDATED = ? where JOB_EXECUTION_ID = ? and VERSION = ?]
2023-04-10T16:38:46.936Z DEBUG 1 --- [           main] o.s.jdbc.core.JdbcTemplate               : Executing prepared SQL query
2023-04-10T16:38:46.936Z DEBUG 1 --- [           main] o.s.jdbc.core.JdbcTemplate               : Executing prepared SQL statement [SELECT  SE.STEP_EXECUTION_ID, SE.STEP_NAME, SE.START_TIME, SE.END_TIME, SE.STATUS, SE.COMMIT_COUNT, SE.READ_COUNT, SE.FILTER_COUNT, SE.WRITE_COUNT, SE.EXIT_CODE, SE.EXIT_MESSAGE, SE.READ_SKIP_COUNT, SE.WRITE_SKIP_COUNT, SE.PROCESS_SKIP_COUNT, SE.ROLLBACK_COUNT, SE.LAST_UPDATED, SE.VERSION, SE.CREATE_TIME, JE.JOB_EXECUTION_ID, JE.START_TIME, JE.END_TIME, JE.STATUS, JE.EXIT_CODE, JE.EXIT_MESSAGE, JE.CREATE_TIME, JE.LAST_UPDATED, JE.VERSION from BATCH_JOB_EXECUTION JE join BATCH_STEP_EXECUTION SE      on SE.JOB_EXECUTION_ID = JE.JOB_EXECUTION_ID where JE.JOB_INSTANCE_ID = ?      and SE.STEP_NAME = ? order by SE.CREATE_TIME desc, SE.STEP_EXECUTION_ID desc]
2023-04-10T16:38:46.940Z DEBUG 1 --- [           main] o.s.jdbc.core.JdbcTemplate               : Executing prepared SQL query
2023-04-10T16:38:46.940Z DEBUG 1 --- [           main] o.s.jdbc.core.JdbcTemplate               : Executing prepared SQL statement [SELECT COUNT(*)  from BATCH_JOB_EXECUTION JE JOIN BATCH_STEP_EXECUTION SE       on SE.JOB_EXECUTION_ID = JE.JOB_EXECUTION_ID where JE.JOB_INSTANCE_ID = ?      and SE.STEP_NAME = ?]
2023-04-10T16:38:46.942Z DEBUG 1 --- [           main] o.s.jdbc.core.JdbcTemplate               : Executing prepared SQL update
2023-04-10T16:38:46.942Z DEBUG 1 --- [           main] o.s.jdbc.core.JdbcTemplate               : Executing prepared SQL statement [INSERT into BATCH_STEP_EXECUTION(STEP_EXECUTION_ID, VERSION, STEP_NAME, JOB_EXECUTION_ID, START_TIME, END_TIME, STATUS, COMMIT_COUNT, READ_COUNT, FILTER_COUNT, WRITE_COUNT, EXIT_CODE, EXIT_MESSAGE, READ_SKIP_COUNT, WRITE_SKIP_COUNT, PROCESS_SKIP_COUNT, ROLLBACK_COUNT, LAST_UPDATED, CREATE_TIME) values(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)]
2023-04-10T16:38:46.943Z DEBUG 1 --- [           main] o.s.jdbc.core.JdbcTemplate               : Executing prepared SQL update
2023-04-10T16:38:46.943Z DEBUG 1 --- [           main] o.s.jdbc.core.JdbcTemplate               : Executing prepared SQL statement [INSERT INTO BATCH_STEP_EXECUTION_CONTEXT (SHORT_CONTEXT, SERIALIZED_CONTEXT, STEP_EXECUTION_ID) VALUES(?, ?, ?)]
2023-04-10T16:38:46.945Z  INFO 1 --- [           main] o.s.batch.core.job.SimpleStepHandler     : Executing step: [BilliProcessing]
2023-04-10T16:38:46.946Z DEBUG 1 --- [           main] o.s.jdbc.core.JdbcTemplate               : Executing prepared SQL update
2023-04-10T16:38:46.947Z DEBUG 1 --- [           main] o.s.jdbc.core.JdbcTemplate               : Executing prepared SQL statement [UPDATE BATCH_STEP_EXECUTION set START_TIME = ?, END_TIME = ?, STATUS = ?, COMMIT_COUNT = ?, READ_COUNT = ?, FILTER_COUNT = ?, WRITE_COUNT = ?, EXIT_CODE = ?, EXIT_MESSAGE = ?, VERSION = ?, READ_SKIP_COUNT = ?, PROCESS_SKIP_COUNT = ?, WRITE_SKIP_COUNT = ?, ROLLBACK_COUNT = ?, LAST_UPDATED = ? where STEP_EXECUTION_ID = ? and VERSION = ?]
2023-04-10T16:38:46.948Z DEBUG 1 --- [           main] o.s.jdbc.core.JdbcTemplate               : Executing prepared SQL query
2023-04-10T16:38:46.948Z DEBUG 1 --- [           main] o.s.jdbc.core.JdbcTemplate               : Executing prepared SQL statement [SELECT VERSION FROM BATCH_JOB_EXECUTION WHERE JOB_EXECUTION_ID=?]
2023-04-10T16:38:46.955Z DEBUG 1 --- [           main] o.s.jdbc.core.JdbcTemplate               : Executing prepared SQL update
2023-04-10T16:38:46.955Z DEBUG 1 --- [           main] o.s.jdbc.core.JdbcTemplate               : Executing prepared SQL statement [UPDATE BATCH_STEP_EXECUTION_CONTEXT SET SHORT_CONTEXT = ?, SERIALIZED_CONTEXT = ? WHERE STEP_EXECUTION_ID = ?]
2023-04-10T16:38:47.023Z DEBUG 1 --- [           main] o.s.jdbc.core.JdbcTemplate               : Executing SQL batch update [INSERT INTO BILL_STATEMENTS (id, first_name, last_name, minutes, data_usage,bill_amount) VALUES (?, ?, ?, ?, ?, ?)]
2023-04-10T16:38:47.023Z DEBUG 1 --- [           main] o.s.jdbc.core.JdbcTemplate               : Executing prepared SQL statement [INSERT INTO BILL_STATEMENTS (id, first_name, last_name, minutes, data_usage,bill_amount) VALUES (?, ?, ?, ?, ?, ?)]
2023-04-10T16:38:47.027Z DEBUG 1 --- [           main] o.s.jdbc.core.JdbcTemplate               : Executing prepared SQL update
2023-04-10T16:38:47.028Z DEBUG 1 --- [           main] o.s.jdbc.core.JdbcTemplate               : Executing prepared SQL statement [UPDATE BATCH_STEP_EXECUTION_CONTEXT SET SHORT_CONTEXT = ?, SERIALIZED_CONTEXT = ? WHERE STEP_EXECUTION_ID = ?]
2023-04-10T16:38:47.029Z DEBUG 1 --- [           main] o.s.jdbc.core.JdbcTemplate               : Executing prepared SQL update
2023-04-10T16:38:47.029Z DEBUG 1 --- [           main] o.s.jdbc.core.JdbcTemplate               : Executing prepared SQL statement [UPDATE BATCH_STEP_EXECUTION set START_TIME = ?, END_TIME = ?, STATUS = ?, COMMIT_COUNT = ?, READ_COUNT = ?, FILTER_COUNT = ?, WRITE_COUNT = ?, EXIT_CODE = ?, EXIT_MESSAGE = ?, VERSION = ?, READ_SKIP_COUNT = ?, PROCESS_SKIP_COUNT = ?, WRITE_SKIP_COUNT = ?, ROLLBACK_COUNT = ?, LAST_UPDATED = ? where STEP_EXECUTION_ID = ? and VERSION = ?]
2023-04-10T16:38:47.030Z DEBUG 1 --- [           main] o.s.jdbc.core.JdbcTemplate               : Executing prepared SQL query
2023-04-10T16:38:47.030Z DEBUG 1 --- [           main] o.s.jdbc.core.JdbcTemplate               : Executing prepared SQL statement [SELECT VERSION FROM BATCH_JOB_EXECUTION WHERE JOB_EXECUTION_ID=?]
2023-04-10T16:38:47.034Z DEBUG 1 --- [           main] o.s.jdbc.core.JdbcTemplate               : Executing SQL batch update [INSERT INTO BILL_STATEMENTS (id, first_name, last_name, minutes, data_usage,bill_amount) VALUES (?, ?, ?, ?, ?, ?)]
2023-04-10T16:38:47.035Z DEBUG 1 --- [           main] o.s.jdbc.core.JdbcTemplate               : Executing prepared SQL statement [INSERT INTO BILL_STATEMENTS (id, first_name, last_name, minutes, data_usage,bill_amount) VALUES (?, ?, ?, ?, ?, ?)]
2023-04-10T16:38:47.037Z DEBUG 1 --- [           main] o.s.jdbc.core.JdbcTemplate               : Executing prepared SQL update
2023-04-10T16:38:47.038Z DEBUG 1 --- [           main] o.s.jdbc.core.JdbcTemplate               : Executing prepared SQL statement [UPDATE BATCH_STEP_EXECUTION_CONTEXT SET SHORT_CONTEXT = ?, SERIALIZED_CONTEXT = ? WHERE STEP_EXECUTION_ID = ?]
2023-04-10T16:38:47.039Z DEBUG 1 --- [           main] o.s.jdbc.core.JdbcTemplate               : Executing prepared SQL update
2023-04-10T16:38:47.039Z DEBUG 1 --- [           main] o.s.jdbc.core.JdbcTemplate               : Executing prepared SQL statement [UPDATE BATCH_STEP_EXECUTION set START_TIME = ?, END_TIME = ?, STATUS = ?, COMMIT_COUNT = ?, READ_COUNT = ?, FILTER_COUNT = ?, WRITE_COUNT = ?, EXIT_CODE = ?, EXIT_MESSAGE = ?, VERSION = ?, READ_SKIP_COUNT = ?, PROCESS_SKIP_COUNT = ?, WRITE_SKIP_COUNT = ?, ROLLBACK_COUNT = ?, LAST_UPDATED = ? where STEP_EXECUTION_ID = ? and VERSION = ?]
2023-04-10T16:38:47.040Z DEBUG 1 --- [           main] o.s.jdbc.core.JdbcTemplate               : Executing prepared SQL query
2023-04-10T16:38:47.040Z DEBUG 1 --- [           main] o.s.jdbc.core.JdbcTemplate               : Executing prepared SQL statement [SELECT VERSION FROM BATCH_JOB_EXECUTION WHERE JOB_EXECUTION_ID=?]
2023-04-10T16:38:47.042Z DEBUG 1 --- [           main] o.s.jdbc.core.JdbcTemplate               : Executing prepared SQL update
2023-04-10T16:38:47.042Z DEBUG 1 --- [           main] o.s.jdbc.core.JdbcTemplate               : Executing prepared SQL statement [UPDATE BATCH_STEP_EXECUTION_CONTEXT SET SHORT_CONTEXT = ?, SERIALIZED_CONTEXT = ? WHERE STEP_EXECUTION_ID = ?]
2023-04-10T16:38:47.045Z  INFO 1 --- [           main] o.s.batch.core.step.AbstractStep         : Step: [BilliProcessing] executed in 99ms
2023-04-10T16:38:47.045Z DEBUG 1 --- [           main] o.s.jdbc.core.JdbcTemplate               : Executing prepared SQL update
2023-04-10T16:38:47.045Z DEBUG 1 --- [           main] o.s.jdbc.core.JdbcTemplate               : Executing prepared SQL statement [UPDATE BATCH_STEP_EXECUTION set START_TIME = ?, END_TIME = ?, STATUS = ?, COMMIT_COUNT = ?, READ_COUNT = ?, FILTER_COUNT = ?, WRITE_COUNT = ?, EXIT_CODE = ?, EXIT_MESSAGE = ?, VERSION = ?, READ_SKIP_COUNT = ?, PROCESS_SKIP_COUNT = ?, WRITE_SKIP_COUNT = ?, ROLLBACK_COUNT = ?, LAST_UPDATED = ? where STEP_EXECUTION_ID = ? and VERSION = ?]
2023-04-10T16:38:47.046Z DEBUG 1 --- [           main] o.s.jdbc.core.JdbcTemplate               : Executing prepared SQL query
2023-04-10T16:38:47.046Z DEBUG 1 --- [           main] o.s.jdbc.core.JdbcTemplate               : Executing prepared SQL statement [SELECT VERSION FROM BATCH_JOB_EXECUTION WHERE JOB_EXECUTION_ID=?]
2023-04-10T16:38:47.048Z DEBUG 1 --- [           main] o.s.jdbc.core.JdbcTemplate               : Executing prepared SQL update
2023-04-10T16:38:47.048Z DEBUG 1 --- [           main] o.s.jdbc.core.JdbcTemplate               : Executing prepared SQL statement [UPDATE BATCH_JOB_EXECUTION_CONTEXT SET SHORT_CONTEXT = ?, SERIALIZED_CONTEXT = ? WHERE JOB_EXECUTION_ID = ?]
2023-04-10T16:38:47.051Z DEBUG 1 --- [           main] o.s.jdbc.core.JdbcTemplate               : Executing prepared SQL query
2023-04-10T16:38:47.052Z DEBUG 1 --- [           main] o.s.jdbc.core.JdbcTemplate               : Executing prepared SQL statement [SELECT VERSION FROM BATCH_JOB_EXECUTION WHERE JOB_EXECUTION_ID=?]
2023-04-10T16:38:47.055Z DEBUG 1 --- [           main] o.s.jdbc.core.JdbcTemplate               : Executing prepared SQL query
2023-04-10T16:38:47.056Z DEBUG 1 --- [           main] o.s.jdbc.core.JdbcTemplate               : Executing prepared SQL statement [SELECT COUNT(*) FROM BATCH_JOB_EXECUTION WHERE JOB_EXECUTION_ID = ?]
2023-04-10T16:38:47.057Z DEBUG 1 --- [           main] o.s.jdbc.core.JdbcTemplate               : Executing prepared SQL update
2023-04-10T16:38:47.057Z DEBUG 1 --- [           main] o.s.jdbc.core.JdbcTemplate               : Executing prepared SQL statement [UPDATE BATCH_JOB_EXECUTION set START_TIME = ?, END_TIME = ?,  STATUS = ?, EXIT_CODE = ?, EXIT_MESSAGE = ?, VERSION = ?, CREATE_TIME = ?, LAST_UPDATED = ? where JOB_EXECUTION_ID = ? and VERSION = ?]
2023-04-10T16:38:47.069Z  INFO 1 --- [           main] o.s.b.c.l.support.SimpleJobLauncher      : Job: [SimpleJob: [name=BillingJob]] completed with the following parameters: [{'run.id':'{value=1, type=class java.lang.Long, identifying=true}'}] and the following status: [COMPLETED] in 129ms
2023-04-10T16:38:47.082Z  INFO 1 --- [ionShutdownHook] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Shutdown initiated...
2023-04-10T16:38:47.088Z  INFO 1 --- [ionShutdownHook] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Shutdown completed.

Native Memory Tracking:
...
```

After confirming, delete the workload.


```
tanzu apps workload delete -n demo billing-job
```

---

CronJobs can now be created from TAP. Customizing TAP opens up new possibilities.

FYI, the workload for backing up this blog and saving it to S3 is [here](https://github.com/categolj/k8s-manifests/blob/main/lime-build/config/app/blog-api/backup-db.yaml).
