diff --git a/images/confluent-kafka/tests/run-tests.sh b/images/confluent-kafka/tests/run-tests.sh index 0d40c0b3d9..b3c14314ec 100755 --- a/images/confluent-kafka/tests/run-tests.sh +++ b/images/confluent-kafka/tests/run-tests.sh @@ -90,7 +90,7 @@ TEST_produce_consume() { docker exec "${CONTAINER_NAME}" kafka-topics --create --topic "${TOPIC_NAME}" --partitions "${PARTITIONS}" --replication-factor "${REPLICATION_FACTOR}" --if-not-exists --bootstrap-server kafka-kraft:"${KAFKA_PORT}" # Produce a test message - echo "Hello Kafka" | docker exec -i "${CONTAINER_NAME}" kafka-console-producer --broker-list kafka-kraft:"${KAFKA_PORT}" --topic "${TOPIC_NAME}" + echo "Hello Kafka" | docker exec -i "${CONTAINER_NAME}" kafka-console-producer --bootstrap-server kafka-kraft:"${KAFKA_PORT}" --topic "${TOPIC_NAME}" # Consume the message consumed_message=$(docker exec "${CONTAINER_NAME}" timeout 10 kafka-console-consumer --bootstrap-server kafka-kraft:"${KAFKA_PORT}" --topic "${TOPIC_NAME}" --from-beginning --max-messages 1) diff --git a/images/go/README.md b/images/go/README.md index 206bad41fe..1d7d9b9fab 100644 --- a/images/go/README.md +++ b/images/go/README.md @@ -32,97 +32,293 @@ Be sure to replace the `ORGANIZATION` placeholder with the name used for your or -## Secure-by-default Features +## Compatibility Notes + +Where possible, the Go Chainguard Image is built for compatibility with the [Docker official image for Golang](https://hub.docker.com/_/golang/). + +The Go Chainguard Image uses the `glibc` implementation of the C standard library, while the Alpine version of the Golang official Docker Image uses `musl`. See our [article on glibc vs. musl](https://edu.chainguard.dev/chainguard/chainguard-images/working-with-images/images-compiled-programs/glibc-vs-musl/) on Chainguard Academy for an overview of the differences between these implementations. + + +The examples in this README recommend executing Go binaries from one of our runtime Chainguard Images, such as the `glibc-dynamic` or `static` Chainguard Images. If using the `static` Chainguard Image, make sure to build your Go binary with static linking. In most cases, this requires running `CGO_ENABLED=0 go build` when building the binary. If dynamic linking is needed, use the `glibc-dynamic` Chainguard Image or the Go Chainguard Image to run your application. + In Go 1.20, we default to using the new `GODEBUG` settings of `tarinsecurepath=0` and `zipinsecurepath=0`. These can be disabled by clearing the `GODEBUG` environment variable, or by setting them to `1`. Learn more about these settings in the [Go release notes](https://tip.golang.org/doc/go1.20). -## Go Application Examples +## Getting Started -This section contains two examples of how you can use the Go Chainguard Image to build an example Go application. For more information on working with this Image, check out our [Getting Started with the Go Chainguard Image](https://edu.chainguard.dev/chainguard/chainguard-images/getting-started/getting-started-go/) guide. +### Example: CLI Application Using Multi-Stage Build +The following build demonstrates a command line application with support for flags and positional arguments. The application prints a modifiable greeting message and provides usage information if the wrong number of arguments are passed by a user or the user passes an unrecognized flag. -### Host architecture example +First, create a project folder and change the working directory to that folder: -Many Image directories in the [public Chainguard Images GitHub repository](https://github.com/chainguard-images/images), including the one for the Go Image, contain examples you can run to test out the given Image. +```sh +mkdir -p ~/go-greeter && cd $_ +``` -You can build the Go application in [tests/hello/main.go](https://github.com/chainguard-images/images/blob/main/images/go/tests/hello/main.go) using the host architecture of your local machine by cloning the GitHub repository and then navigating to the `/images/go/` directory. +Next, ,write a file defining our Go CLI application (`main.go`: -From there, run the following command: +```sh +cat << 'EOF' > main.go +package main + +import ( + "flag" + "fmt" + "log" + "os" +) + +func usage() { + fmt.Fprintf(os.Stderr, "usage: hello [options] [name]\n") + flag.PrintDefaults() + os.Exit(2) +} + +var ( + greeting = flag.String("g", "Hello", "Greet with `greeting`") +) + +func main() { + // Configure logging + log.SetFlags(0) + log.SetPrefix("hello: ") + + // Parse flags. + flag.Usage = usage + flag.Parse() + + // Parse and validate arguments. + name := "Linky ๐Ÿ™" + args := flag.Args() + if len(args) >= 2 { + usage() + } + if len(args) >= 1 { + name = args[0] + } + if name == "" { // hello '' is an error + log.Fatalf("invalid name %q", name) + } + + fmt.Printf("%s, %s!\n", *greeting, name) +} +EOF +``` + +Create a go.mod` file to list dependencies: ```sh -docker run --rm -v "${PWD}:/work" -w /work/tests/hello \ - -e GOOS="$(go env GOOS)" -e GOARCH="$(go env GOARCH)" \ - cgr.dev/chainguard/go build -o /work/hello . +cat << 'EOF' > go.mod +module chainguard.dev/greet + +go 1.19 +EOF ``` -The example application will be built to `./hello`: +Write a `Dockerfile` to define our image build: ```sh -./hello +cat << 'EOF' > Dockerfile +FROM cgr.dev/chainguard/go AS builder +COPY . /app +RUN cd /app && go build -o go-greeter . + +FROM cgr.dev/chainguard/static +COPY --from=builder /app/go-greeter /usr/bin/ +ENTRYPOINT ["/usr/bin/go-greeter"] +EOF +``` + +The `Dockerfile` uses a multi-stage build approach, compiling the application using the `go` Chainguard Image, then copying the binary to the `static` Chainguard Image for execution. Note that the `static` image requires that the Go binary be statically linkedโ€”if your application requires dynamic linking, consider using the `glibc-dynamic` Chainguard Image for your runtime (see the second example in this README). + +Build the image, tagging it `go-greeter`: + +```sh +docker build . -t go-greeter +``` + +Run the image: + +```sh +docker run go-greeter ``` + +You should see output similar to the following: + +``` +Hello, Linky ๐Ÿ™! ``` -Hello World! + +You can also pass in arguments that will be parsed by the Go CLI application: + +```sh +docker run go-greeter -g Greetings "Chainguard user" ``` +This will produce the following output: -### Dockerfile example +``` +Greetings, Chainguard user! +``` -The following example Dockerfile builds a hello-world program in Go and copies it on top of the `cgr.dev/chainguard/static:latest` base image: +The application will also share usage instructions when prompted with the `--help` flag or when invalid flags are passed. -```dockerfile -FROM cgr.dev/chainguard/go:latest as build +Because we used the `static` Chainguard Image as our runtime, the final image only requires a few megabytes on disk: -WORKDIR /work -COPY < main.go package main -import "fmt" + +import ( + "flag" + "fmt" + "html" + "log" + "net/http" + "os" + "runtime/debug" + "strings" +) + +func usage() { + fmt.Fprintf(os.Stderr, "usage: helloserver [options]\n") + flag.PrintDefaults() + os.Exit(2) +} + +var ( + greeting = flag.String("g", "Hello", "Greet with `greeting`") + addr = flag.String("addr", "0.0.0.0:8080", "address to serve") +) + func main() { - fmt.Println("Hello World!") + // Parse flags. + flag.Usage = usage + flag.Parse() + + // Parse and validate arguments (none). + args := flag.Args() + if len(args) != 0 { + usage() + } + + // Register handlers. for greeting and version + http.HandleFunc("/", greet) + http.HandleFunc("/version", version) + + log.Printf("serving http://%s\n", *addr) + log.Fatal(http.ListenAndServe(*addr, nil)) } -EOF -RUN go build -o hello . -FROM cgr.dev/chainguard/static:latest +func version(w http.ResponseWriter, r *http.Request) { + info, ok := debug.ReadBuildInfo() + if !ok { + http.Error(w, "no build information available", 500) + return + } -COPY --from=build /work/hello /hello -CMD ["/hello"] + fmt.Fprintf(w, "\n
\n")
+	fmt.Fprintf(w, "%s\n", html.EscapeString(info.String()))
+}
+
+func greet(w http.ResponseWriter, r *http.Request) {
+	name := strings.Trim(r.URL.Path, "/")
+	if name == "" {
+		name = "Linky ๐Ÿ™"
+	}
+
+	fmt.Fprintf(w, "\n")
+	fmt.Fprintf(w, "%s, %s!\n", *greeting, html.EscapeString(name))
+}
+EOF
 ```
 
-Run the following command to build the demo image and tag it as `go-hello-world`:
+Next, write a `go.mod` file listing dependencies:
 
 ```sh
-docker build -t go-hello-world  .
+cat << 'EOF' > go.mod
+module chainguard.dev/greet-server
+
+go 1.19
+EOF
 ```
 
-Now you can run the image with:
+Write a `Dockerfile` to define our image build:
 
 ```sh
-docker run go-hello-world
-```
+cat << 'EOF' > Dockerfile
+FROM cgr.dev/chainguard/go AS builder
+COPY . /app
+RUN cd /app && go build
+
+FROM cgr.dev/chainguard/glibc-dynamic
+COPY --from=builder /app/greet-server /usr/bin/
 
-You should get output like this:
+EXPOSE 8080
 
+ENTRYPOINT ["/usr/bin/greet-server"]
+EOF
 ```
-Hello World!
+
+The `Dockerfile` uses a multi-stage build approach, compiling the application using the `go` Chainguard Image, then copying the binary to the `glibc-dynamic` Chainguard Image to serve.
+
+Build the image, tagging it `greet-server`:
+
+```sh
+docker build . -t greet-server
 ```
 
-Itโ€™s worth noting how small the resulting image is:
+Run the image:
 
 ```sh
-docker images go-hello-world
+docker run -p 8080:8080 greet-server
 ```
+
+Visit [http://0.0.0.0:8080/](http://0.0.0.0:8080/) using a web browser on your host machine. You should see the following:
+
+```
+Hello, Linky ๐Ÿ™!
+```
+
+Changes to the URI will be routed to the application. Try visiting [http://0.0.0.0:8080/Chainguard%20Customer](http://0.0.0.0:8080/Chainguard%20Customer). You should see the following output:
+
 ```
-REPOSITORY       TAG       IMAGE ID       CREATED       SIZE
-go-hello-world   latest    859fedabd532   5 hours ago   3.21MB
+Hello, Chainguard Customer!
 ```
+
+The application will also share version information at [http://0.0.0.0:8080/version](http://0.0.0.0:8080/version).
+
+If you're building a web application with Go, consider the [nginx](https://images.chainguard.dev/directory/image/nginx/overview) Chainguard Image for use as a reverse proxy.
+
+## Documentation and Resources
+
+- [Chainguard Academy: Getting Started with the Go Chainguard Image](https://edu.chainguard.dev/chainguard/chainguard-images/getting-started/go/)
+- [Video: Migrating a Dockerfile for a Go application to use Chainguard Images](https://edu.chainguard.dev/chainguard/chainguard-images/videos/migrating_go/)
+- [Blog Post: Statically Linking Go in 2022](https://mt165.co.uk/blog/static-link-go/)
+- [Blog Post: Building minimal and low CVE images for compiled languages](https://www.chainguard.dev/unchained/building-minimal-and-low-cve-images-for-compiled-languages)
+- [Chainguard Academy: glibc vs. musl](https://edu.chainguard.dev/chainguard/chainguard-images/working-with-images/images-compiled-programs/glibc-vs-musl/)
 
 
 ## Contact Support
diff --git a/images/keycloak-operator/tests/keycloak-test.sh b/images/keycloak-operator/tests/keycloak-test.sh
index 70062390f2..377c62561f 100755
--- a/images/keycloak-operator/tests/keycloak-test.sh
+++ b/images/keycloak-operator/tests/keycloak-test.sh
@@ -91,12 +91,12 @@ TEST_keycloak_api() {
     return 1
   fi
 
-  # Ensure that an 'admin' user was returned in the API response.
-  extracted_username=$(echo "${users_output}" | jq -r '.[] | select(.username=="admin") | .username')
-  if [[ "${extracted_username}" == "admin" ]]; then
-    echo "Keycloak API correctly returned 'admin' user details."
+  # Ensure that a 'temp-admin' user was returned in the API response.
+  extracted_username=$(echo "${users_output}" | jq -r '.[] | select(.username=="temp-admin") | .username')
+  if [[ "${extracted_username}" == "temp-admin" ]]; then
+    echo "Keycloak API correctly returned 'temp-admin' user details."
   else
-    echo "FAILED: No entry with username 'admin' found in the response: ${users_output}"
+    echo "FAILED: No entry with username 'temp-admin' found in the response: ${users_output}"
     exit 1
   fi
 }
diff --git a/images/kubernetes-event-exporter/README.md b/images/kubernetes-event-exporter/README.md
index e737ac1b21..eaff75338c 100644
--- a/images/kubernetes-event-exporter/README.md
+++ b/images/kubernetes-event-exporter/README.md
@@ -34,27 +34,21 @@ Be sure to replace the `ORGANIZATION` placeholder with the name used for your or
 
 ## Usage
 
-Project documentation suggests usage of Bitnami chart which is comprehensive.
+The upstream project provides Kubernetes manifests which can be used to deploy
+this image. Please see the [GitHub README](https://github.com/resmoio/kubernetes-event-exporter)
+for more information. This directs users to the `deploy` sub-directory of the
+repository.
 
-This chart bootstraps a Kubernetes Event Exporter deployment on a Kubernetes cluster using 
-the Helm package manager.
+If using these reference manifests, you'll need to ensure you update the image
+reference in [02-deployment.yaml](https://github.com/resmoio/kubernetes-event-exporter/blob/master/deploy/02-deployment.yaml)
+to use this Chainguard image.
 
-To install the chart with the release name my-release:
+### Bitnami helm chart
 
-```
-helm install my-release oci://registry-1.docker.io/bitnamicharts/kubernetes-event-exporter
-
-helm upgrade my-release oci://registry-1.docker.io/bitnamicharts/kubernetes-event-exporter \
-  --set image.repository=cgr.dev/chainguard/kubernetes-event-exporter \
-  --set image.tag=latest
-
-```
+This image **is not compatible** with the bitnami helm chart for
+kubernetes-event-exporter. For a bitnami compatible image, please see:
+[kubernetes-event-exporter-bitnami](https://images.chainguard.dev/directory/image/kubernetes-event-exporter-bitnami).
 
-To uninstall/delete this deployment:
-
-```
-helm delete my-release
-```
 
 
 ## Contact Support
diff --git a/images/neuvector/tests/check-core.sh b/images/neuvector/tests/check-core.sh
index 56bbae16de..f1d14544e3 100755
--- a/images/neuvector/tests/check-core.sh
+++ b/images/neuvector/tests/check-core.sh
@@ -69,11 +69,11 @@ kubectl rollout status deployment/"${TEST_IMAGE}"-pod -n test
 
 # Generate network violations
 TEST_POD=$(kubectl get pod -n test -l app="${TEST_IMAGE}"-pod -o jsonpath='{.items[0].metadata.name}')
-kubectl exec $TEST_POD -n test -- curl www.google.com
+kubectl exec $TEST_POD -n test -- curl www.google.com || true
 
 # Simulate an attack
 INTERNAL_TEST_IP=$(kubectl get pod -n test -l app="${TEST_IMAGE}"-pod -o jsonpath='{.items[1].status.podIP}')
-kubectl exec -i $TEST_POD -n test -- bash -c "ping $INTERNAL_TEST_IP -s 40000 -c 10"
+kubectl exec -i $TEST_POD -n test -- bash -c "ping $INTERNAL_TEST_IP -s 40000 -c 10" || true
 
 # Check for broadcast
 kubectl logs daemonset/neuvector-enforcer-pod -n "${NAMESPACE}" | grep "AGT|main.parseHostAddrs: link - flags=up|broadcast|multicast"
@@ -83,4 +83,4 @@ kubectl logs deployment/neuvector-scanner-pod -n "${NAMESPACE}" | grep "Stream s
 kubectl logs deployment/neuvector-controller-pod -n "${NAMESPACE}" | grep "Stream receive"
 
 # Cleanup
-kubectl delete namespace test
+kubectl delete namespace test --cascade='background' --grace-period=1
diff --git a/images/openai/config/main.tf b/images/openai/config/main.tf
index 62fd4b40b2..b4b13887b2 100644
--- a/images/openai/config/main.tf
+++ b/images/openai/config/main.tf
@@ -13,7 +13,7 @@ terraform {
 }
 
 variable "extra_packages" {
-  default     = ["py3-openai"]
+  default     = ["py3-openai", "py3-openai-bin"]
   description = "The additional packages to install (e.g. py3-openai)."
 }
 
diff --git a/images/pulumi/tests/examples/smoketest-java/pom.xml b/images/pulumi/tests/examples/smoketest-java/pom.xml
index 57928d22ea..4553b90ccb 100644
--- a/images/pulumi/tests/examples/smoketest-java/pom.xml
+++ b/images/pulumi/tests/examples/smoketest-java/pom.xml
@@ -20,13 +20,13 @@
         
             com.pulumi
             pulumi
-            0.9.9
+            0.16.1
         
 
         
             com.pulumi
             kubernetes
-            4.7.1
+            4.18.1
         
     
 
diff --git a/images/spark-operator/config/main.tf b/images/spark-operator/config/main.tf
index 25ee03ad3b..516ce9046b 100644
--- a/images/spark-operator/config/main.tf
+++ b/images/spark-operator/config/main.tf
@@ -25,6 +25,16 @@ output "config" {
       JAVA_HOME  = "/usr/lib/jvm/default-jvm"
     }
 
+    paths = [
+      {
+        path        = "/etc/k8s-webhook-server"
+        type        = "directory"
+        uid         = 65532
+        gid         = 65532
+        permissions = 493
+      }
+    ]
+
     entrypoint = {
       command = "/usr/bin/entrypoint.sh"
     }
diff --git a/images/spark-operator/tests/main.tf b/images/spark-operator/tests/main.tf
index 6c365bbbad..fc8422d05c 100644
--- a/images/spark-operator/tests/main.tf
+++ b/images/spark-operator/tests/main.tf
@@ -58,8 +58,8 @@ module "helm-spark-operator" {
 
   values = {
     image = {
-      registry   = ""
-      repository = local.parsed.registry_repo
+      registry   = local.parsed.registry
+      repository = local.parsed.repo
       tag        = local.parsed.pseudo_tag
     },
   }
@@ -80,10 +80,14 @@ resource "imagetest_feature" "basic" {
       cmd  = module.helm-spark-operator.install_cmd
     },
     {
-      name    = "Run real test"
-      workdir = "/tests"
-      cmd     = "./test-spark.sh"
-    }
+      name = "Schedule a job"
+      cmd  = "kubectl apply -f https://raw.githubusercontent.com/kubeflow/spark-operator/refs/heads/master/examples/spark-pi.yaml"
+    },
+    {
+      name  = "Wait for job to complete"
+      cmd   = "kubectl wait --for=jsonpath='{.status.executorState.*}'=COMPLETED sparkapp/spark-pi --timeout=60s"
+      retry = { attempts = 5, delay = "30s" }
+    },
   ]
 
   labels = {
diff --git a/images/spark-operator/tests/test-spark.sh b/images/spark-operator/tests/test-spark.sh
deleted file mode 100755
index d23027ab34..0000000000
--- a/images/spark-operator/tests/test-spark.sh
+++ /dev/null
@@ -1,168 +0,0 @@
-#!/usr/bin/env bash
-
-set -o errexit -o nounset -o errtrace -o pipefail -x
-
-REQUEST_RETRIES=10
-RETRY_DELAY=15
-
-expected_logs=(
-  "Driver spark-pi-driver is running"
-  "Starting processing key"
-  "Ending processing key"
-)
-missing_logs=()
-
-# Looks for known, good log entries. Fails the script if not found after a
-# number of retries.
-check_logs() {
-  for log_entry in "${expected_logs[@]}"; do
-    local retries=${REQUEST_RETRIES}
-    local found=0
-    while [ $retries -gt 0 ]; do
-      if kubectl logs -l app.kubernetes.io/name=spark-operator -n "${NAMESPACE}" | grep -q "${log_entry}"; then
-        found=1
-        break
-      else
-        retries=$((retries-1))
-        sleep ${RETRY_DELAY}
-      fi
-    done
-
-    if [ $found -eq 0 ]; then
-      missing_logs+=("${log_entry}")
-    fi
-  done
-
-  if [ ${#missing_logs[@]} -ne 0 ]; then
-    echo "The following expected log entries were not found after all retries:"
-    for missing_log in "${missing_logs[@]}"; do
-      echo "- ${missing_log}"
-    done
-    exit 1
-  fi
-}
-
-# Deploys the required k8s resources.
-apply_manifests() {
-  kubectl apply -f - <<-EOF
-	apiVersion: v1
-	kind: Namespace
-	metadata:
-	  name: ${NAMESPACE}
-	---
-	apiVersion: v1
-	kind: ServiceAccount
-	metadata:
-	  name: spark-pi
-	  namespace: ${NAMESPACE}
-	---
-  apiVersion: rbac.authorization.k8s.io/v1
-  kind: ClusterRole
-  metadata:
-    name: spark-pi
-  rules:
-  - apiGroups: [""]
-    resources: ["pods", "services", "configmaps", "secrets", "events"]
-    verbs: ["*"]
-  - apiGroups: ["sparkoperator.k8s.io"]
-    resources: ["sparkapplications", "scheduledsparkapplications", "sparkapplications/status"]
-    verbs: ["*"]
-	---
-  apiVersion: rbac.authorization.k8s.io/v1
-  kind: ClusterRoleBinding
-  metadata:
-    name: spark-pi
-  subjects:
-  - kind: ServiceAccount
-    name: spark-pi
-    namespace: ${NAMESPACE}
-  roleRef:
-    kind: ClusterRole
-    name: spark-pi
-    apiGroup: rbac.authorization.k8s.io
-	---
-	apiVersion: rbac.authorization.k8s.io/v1
-	kind: ClusterRole
-	metadata:
-	  name: spark-operator-clusterrole
-	rules:
-	- apiGroups: [""]
-	  resources: ["pods", "services", "configmaps", "secrets"]
-	  verbs: ["get", "watch", "list", "create", "delete", "patch", "update"]
-	- apiGroups: ["sparkoperator.k8s.io"]
-	  resources: ["sparkapplications", "scheduledsparkapplications"]
-	  verbs: ["get", "watch", "list", "create", "delete", "patch", "update"]
-	---
-	apiVersion: rbac.authorization.k8s.io/v1
-	kind: ClusterRoleBinding
-	metadata:
-	  name: spark-operator-clusterrolebinding
-	subjects:
-	- kind: ServiceAccount
-	  name: spark-operator
-	  namespace: ${NAMESPACE}
-	roleRef:
-	  kind: ClusterRole
-	  name: spark-operator-clusterrole
-	  apiGroup: rbac.authorization.k8s.io
-	---
-	apiVersion: "sparkoperator.k8s.io/v1beta2"
-	kind: SparkApplication
-	metadata:
-	  name: spark-pi
-	  namespace: ${NAMESPACE}
-	spec:
-	  type: Scala
-	  mode: cluster
-	  image: ${IMAGE}
-	  imagePullPolicy: Always
-	  mainClass: org.apache.spark.examples.SparkPi
-	  mainApplicationFile: "local:///opt/spark/examples/jars/spark-examples_2.12-3.1.1.jar"
-	  sparkVersion: "3.1.1"
-	  restartPolicy:
-	    type: Never
-	  volumes:
-	    - name: "test-volume"
-	      hostPath:
-	        path: "/tmp"
-	        type: Directory
-	  driver:
-	    cores: 1
-	    coreLimit: "1200m"
-	    memory: "512m"
-	    labels:
-	      version: 3.1.1
-	    serviceAccount: spark-pi
-	    volumeMounts:
-	      - name: "test-volume"
-	        mountPath: "/tmp"
-	  executor:
-	    cores: 1
-	    instances: 1
-	    memory: "512m"
-	    labels:
-	      version: 3.1.1
-	    volumeMounts:
-	      - name: "test-volume"
-	        mountPath: "/tmp"
-	EOF
-}
-
-function cleanup() {
-  kubectl delete clusterrole spark-pi
-  kubectl delete clusterrole spark-operator-clusterrole
-  kubectl delete clusterrolebinding spark-pi
-  kubectl delete clusterrolebinding spark-operator-clusterrolebinding
-  kubectl delete sparkapplication spark-pi -n ${NAMESPACE}
-  helm uninstall spark -n ${NAMESPACE}
-  helm uninstall spark-operator -n ${NAMESPACE}
-  kubectl delete ns ${NAMESPACE}
-  kubectl delete crd scheduledsparkapplications.sparkoperator.k8s.io
-  kubectl delete crd sparkapplications.sparkoperator.k8s.io
-}
-
-trap cleanup EXIT
-
-# Apply the Kubernetes manifests
-apply_manifests
-check_logs