diff --git a/.ci/openshift_integration.sh b/.ci/openshift_integration.sh index f378a9a69..87e5b094e 100755 --- a/.ci/openshift_integration.sh +++ b/.ci/openshift_integration.sh @@ -62,4 +62,4 @@ $(realpath odo) registry delete DefaultDevfileRegistry -f $(realpath odo) registry add TestDevfileRegistry http://$REGISTRY_HOSTNAME # Run the devfile validation tests -ENV=openshift REGISTRY=remote tests/test.sh $(realpath odo) $YQ_PATH +ENV=openshift REGISTRY=remote tests/check_odo_happy_path.sh $(realpath odo) $YQ_PATH diff --git a/.github/workflows/validate-devfiles-minikube.yaml b/.github/workflows/validate-devfiles-minikube.yaml index 9aec5980b..f3a82c1c8 100644 --- a/.github/workflows/validate-devfiles-minikube.yaml +++ b/.github/workflows/validate-devfiles-minikube.yaml @@ -31,10 +31,10 @@ jobs: - name: Checkout uses: actions/checkout@v1 - name: Setup Minikube - uses: manusa/actions-setup-minikube@v2.4.2 + uses: manusa/actions-setup-minikube@v2.7.0 with: - minikube version: 'v1.21.0' - kubernetes version: 'v1.21.0' + minikube version: 'v1.26.1' + kubernetes version: '1.25.0' driver: 'docker' github token: ${{ secrets.GITHUB_TOKEN }} start args: '--addons=ingress' @@ -43,5 +43,7 @@ jobs: with: # Installs the latest release of odo odo: "2.5.1" - - name: Validate the devfile stacks - run: tests/test.sh odo + - name: Check that containers components are non terminating + run: tests/check_non_terminating.sh + - name: Run odo happy path tests + run: tests/check_odo_happy_path.sh odo diff --git a/stacks/dotnet50/devfile.yaml b/stacks/dotnet50/devfile.yaml index b28cc5dd3..1db617106 100644 --- a/stacks/dotnet50/devfile.yaml +++ b/stacks/dotnet50/devfile.yaml @@ -24,6 +24,7 @@ components: - name: dotnet container: image: registry.access.redhat.com/ubi8/dotnet-50:5.0 + args: ["tail", "-f", "/dev/null"] mountSources: true env: - name: CONFIGURATION diff --git a/stacks/dotnet60/devfile.yaml b/stacks/dotnet60/devfile.yaml index af6fc8bc0..b339a1e83 100644 --- a/stacks/dotnet60/devfile.yaml +++ b/stacks/dotnet60/devfile.yaml @@ -24,6 +24,7 @@ components: - name: dotnet container: image: registry.access.redhat.com/ubi8/dotnet-60:6.0 + args: ["tail", "-f", "/dev/null"] mountSources: true env: - name: CONFIGURATION diff --git a/stacks/dotnetcore31/devfile.yaml b/stacks/dotnetcore31/devfile.yaml index f19e07e4e..6369daa37 100644 --- a/stacks/dotnetcore31/devfile.yaml +++ b/stacks/dotnetcore31/devfile.yaml @@ -24,6 +24,7 @@ components: - name: dotnet container: image: registry.access.redhat.com/ubi8/dotnet-31:3.1 + args: ["tail", "-f", "/dev/null"] mountSources: true env: - name: CONFIGURATION diff --git a/stacks/go/devfile.yaml b/stacks/go/devfile.yaml index c07be3a88..f4abdf1e3 100644 --- a/stacks/go/devfile.yaml +++ b/stacks/go/devfile.yaml @@ -23,6 +23,7 @@ components: - name: http-go targetPort: 8080 image: quay.io/devfile/golang:latest + args: ["tail", "-f", "/dev/null"] memoryLimit: 1024Mi mountSources: true name: runtime diff --git a/stacks/java-openliberty-gradle/devfile.yaml b/stacks/java-openliberty-gradle/devfile.yaml index b6c8919bb..00b820f79 100644 --- a/stacks/java-openliberty-gradle/devfile.yaml +++ b/stacks/java-openliberty-gradle/devfile.yaml @@ -40,6 +40,7 @@ components: - name: dev container: image: icr.io/appcafe/open-liberty-devfile-stack:{{liberty-version}}-gradle + args: ["tail", "-f", "/dev/null"] memoryLimit: 1280Mi mountSources: true endpoints: diff --git a/stacks/java-openliberty/devfile.yaml b/stacks/java-openliberty/devfile.yaml index 77a47a21c..31be7fdcd 100644 --- a/stacks/java-openliberty/devfile.yaml +++ b/stacks/java-openliberty/devfile.yaml @@ -42,6 +42,7 @@ components: container: # In the original upstream of this devfile, the image used is openliberty/devfile-stack:, which is built from the repository: https://github.com/OpenLiberty/devfile-stack image: icr.io/appcafe/open-liberty-devfile-stack:{{liberty-version}} + args: ["tail", "-f", "/dev/null"] memoryLimit: 768Mi mountSources: true endpoints: diff --git a/stacks/java-quarkus/devfile.yaml b/stacks/java-quarkus/devfile.yaml index 52c53db59..7134e2d82 100644 --- a/stacks/java-quarkus/devfile.yaml +++ b/stacks/java-quarkus/devfile.yaml @@ -20,6 +20,7 @@ components: - name: tools container: image: registry.access.redhat.com/ubi8/openjdk-11 + args: ["tail", "-f", "/dev/null"] memoryLimit: 512Mi ## default app nowhere needs this but leaving room for expansion. mountSources: true volumeMounts: diff --git a/stacks/java-websphereliberty-gradle/devfile.yaml b/stacks/java-websphereliberty-gradle/devfile.yaml index 44799da91..2df9a7a8c 100644 --- a/stacks/java-websphereliberty-gradle/devfile.yaml +++ b/stacks/java-websphereliberty-gradle/devfile.yaml @@ -40,6 +40,7 @@ components: - name: dev container: image: icr.io/appcafe/websphere-liberty-devfile-stack:{{liberty-version}}-gradle + args: ["tail", "-f", "/dev/null"] memoryLimit: 1280Mi mountSources: true endpoints: diff --git a/stacks/java-websphereliberty/devfile.yaml b/stacks/java-websphereliberty/devfile.yaml index 338c2e746..ef5ce7ea0 100644 --- a/stacks/java-websphereliberty/devfile.yaml +++ b/stacks/java-websphereliberty/devfile.yaml @@ -42,6 +42,7 @@ components: container: # In the original upstream of this devfile, the image used is openliberty/devfile-stack:, which is built from the repository: https://github.com/OpenLiberty/devfile-stack image: icr.io/appcafe/websphere-liberty-devfile-stack:{{liberty-version}} + command: ["tail", "-f", "/dev/null"] memoryLimit: 768Mi mountSources: true endpoints: diff --git a/stacks/java-wildfly-bootable-jar/devfile.yaml b/stacks/java-wildfly-bootable-jar/devfile.yaml index d882d4203..72c95b90b 100644 --- a/stacks/java-wildfly-bootable-jar/devfile.yaml +++ b/stacks/java-wildfly-bootable-jar/devfile.yaml @@ -93,6 +93,7 @@ components: - name: wildfly container: image: registry.access.redhat.com/ubi8/openjdk-11 + args: ["tail", "-f", "/dev/null"] memoryLimit: 1512Mi mountSources: true volumeMounts: diff --git a/stacks/nodejs-angular/devfile.yaml b/stacks/nodejs-angular/devfile.yaml index c449b34af..c428045f6 100644 --- a/stacks/nodejs-angular/devfile.yaml +++ b/stacks/nodejs-angular/devfile.yaml @@ -28,6 +28,7 @@ components: - name: http-angular targetPort: 4200 image: registry.access.redhat.com/ubi8/nodejs-16:latest + args: ["tail", "-f", "/dev/null"] memoryLimit: 1024Mi name: runtime commands: diff --git a/stacks/nodejs-nextjs/devfile.yaml b/stacks/nodejs-nextjs/devfile.yaml index 3e8a17f3f..a7b21d68d 100644 --- a/stacks/nodejs-nextjs/devfile.yaml +++ b/stacks/nodejs-nextjs/devfile.yaml @@ -27,6 +27,7 @@ components: - name: http-nextjs targetPort: 3000 image: registry.access.redhat.com/ubi8/nodejs-16:latest + command: ["tail", "-f", "/dev/null"] memoryLimit: 1024Mi name: runtime commands: diff --git a/stacks/nodejs-nuxtjs/devfile.yaml b/stacks/nodejs-nuxtjs/devfile.yaml index f768ef532..039cbe38b 100644 --- a/stacks/nodejs-nuxtjs/devfile.yaml +++ b/stacks/nodejs-nuxtjs/devfile.yaml @@ -26,6 +26,7 @@ components: - name: http-nuxtjs targetPort: 3000 image: registry.access.redhat.com/ubi8/nodejs-16:latest + args: ["tail", "-f", "/dev/null"] memoryLimit: 1024Mi name: runtime commands: diff --git a/stacks/nodejs-react/devfile.yaml b/stacks/nodejs-react/devfile.yaml index cefed7a36..2c69fb85b 100644 --- a/stacks/nodejs-react/devfile.yaml +++ b/stacks/nodejs-react/devfile.yaml @@ -26,6 +26,7 @@ components: - name: http-react targetPort: 3000 image: registry.access.redhat.com/ubi8/nodejs-16:latest + args: ["tail", "-f", "/dev/null"] memoryLimit: 1024Mi name: runtime commands: diff --git a/stacks/nodejs-svelte/devfile.yaml b/stacks/nodejs-svelte/devfile.yaml index 93bec018c..17b7f4f78 100644 --- a/stacks/nodejs-svelte/devfile.yaml +++ b/stacks/nodejs-svelte/devfile.yaml @@ -25,6 +25,7 @@ components: - name: http-svelte targetPort: 3000 image: registry.access.redhat.com/ubi8/nodejs-16:latest + args: ["tail", "-f", "/dev/null"] memoryLimit: 1024Mi name: runtime commands: diff --git a/stacks/nodejs-vue/devfile.yaml b/stacks/nodejs-vue/devfile.yaml index 5b5f58cf6..75684b469 100644 --- a/stacks/nodejs-vue/devfile.yaml +++ b/stacks/nodejs-vue/devfile.yaml @@ -28,6 +28,7 @@ components: - name: http-vue targetPort: 3000 image: registry.access.redhat.com/ubi8/nodejs-16:latest + args: ["tail", "-f", "/dev/null"] memoryLimit: 1024Mi name: runtime commands: diff --git a/stacks/nodejs/devfile.yaml b/stacks/nodejs/devfile.yaml index 5d7331366..f05a3fab6 100644 --- a/stacks/nodejs/devfile.yaml +++ b/stacks/nodejs/devfile.yaml @@ -17,6 +17,7 @@ components: - name: runtime container: image: registry.access.redhat.com/ubi8/nodejs-16:latest + args: ["tail", "-f", "/dev/null"] memoryLimit: 1024Mi mountSources: true endpoints: diff --git a/stacks/php-laravel/devfile.yaml b/stacks/php-laravel/devfile.yaml index f313472d7..5a58cbf6b 100644 --- a/stacks/php-laravel/devfile.yaml +++ b/stacks/php-laravel/devfile.yaml @@ -28,6 +28,7 @@ components: - name: http-laravel targetPort: 8000 image: quay.io/devfile/composer:2.1.11 + args: ["tail", "-f", "/dev/null"] memoryLimit: 1024Mi mountSources: true name: runtime diff --git a/stacks/python-django/devfile.yaml b/stacks/python-django/devfile.yaml index d099f22fd..e772a0d08 100644 --- a/stacks/python-django/devfile.yaml +++ b/stacks/python-django/devfile.yaml @@ -24,6 +24,7 @@ components: - name: py-web container: image: registry.access.redhat.com/ubi9/python-39:latest + args: ["tail", "-f", "/dev/null"] mountSources: true endpoints: - name: http-django diff --git a/stacks/python-flask/devfile.yaml b/stacks/python-flask/devfile.yaml index 2c70a45ed..d9aad23e1 100644 --- a/stacks/python-flask/devfile.yaml +++ b/stacks/python-flask/devfile.yaml @@ -23,6 +23,7 @@ components: - name: py-web container: image: registry.access.redhat.com/ubi9/python-39:latest + args: ["tail", "-f", "/dev/null"] mountSources: true endpoints: - name: http-python diff --git a/tests/README.md b/tests/README.md index 6c13538a0..0dbffcfcb 100644 --- a/tests/README.md +++ b/tests/README.md @@ -12,12 +12,20 @@ 1) Ensure minikube is running and `minikube ip` reports a valid ip address -2) From the root of this repository, run `tests/test.sh`. +2) From the root of this repository, run `tests/check_odo_happy_path.sh`. - The test script will validate each devfile stack under `stacks/` with odo, verifying that the stack can be used to build a starter project and that the application is properly built and exposed. - The test script checks for an HTTP 200 status code to determine "properly exposed". - Each devfile stack **must** have at least one starter project specified in the devfile.yaml +3) From the root of this repository, run `tests/check_non_terminating.sh`. + + - The test script will validate each devfile stack under `stacks/`, verifying that the components of type container are terminating. + - The test script retrieves the `image`, `command` and `args` of a container component and uses them to run a pod and wait until it reaches the `Running` state: + ```bash + kubectl run test-terminating -n default --attach=false --restart=Never --image="" --command=true -- "" "" + ``` + - Each container component **must** be non-terminating. If the default `image` entrypoint is terminating an `args` (preferred) or `command` should be specified in the defile (e.g. `["tail", "-f", "/dev/null"]`). ## Limitations diff --git a/tests/check_non_terminating.sh b/tests/check_non_terminating.sh new file mode 100755 index 000000000..9c6374e2a --- /dev/null +++ b/tests/check_non_terminating.sh @@ -0,0 +1,166 @@ +#!/bin/bash +set -o nounset +set -o errexit + +DEVFILES_DIR="$(pwd)/stacks" + +replaceVariables() { + image=$1 + VAR_KEYS=(liberty-version) + VAR_VALUES=(22.0.0.1) + + for i in "${!VAR_KEYS[@]}"; do + key='{{' + key+=${VAR_KEYS[i]} + key+='}}' + value=${VAR_VALUES[i]} + image=${image/${key}/${value}} + done + echo "$image" +} + +getContainerComponentsNum() { + devfilePath=$1 + component_num=$($YQ_PATH eval '[ .components[] | select(has("container")) ] | length' "$devfilePath" -r) + echo "${component_num}" +} + +getName() { + devfilePath=$1 + name=$($YQ_PATH eval '.metadata.name' "$devfilePath" -r) + echo "${name}" +} + +getFirstContainerComponentImage() { + devfilePath=$1 + + image_original=$($YQ_PATH eval '[ .components[] | select(has("container")) ] | .[0].container.image' "$devfilePath" -r) + image_processed=$(replaceVariables "${image_original}") + echo "${image_processed}" +} + +getFirstContainerComponentCommand() { + devfilePath=$1 + local _gfccc_command=() + local _gfccc_command_string=() + + IFS=" " read -r -a _gfccc_command_string <<< "$($YQ_PATH eval '[ .components[] | select(has("container")) ] | .[0].container.command[]? + " "' "$devfilePath" -r | paste -s -d '\0' -)" + if (( ${#_gfccc_command_string[@]} == 0 )); then + echo "" + else + for command_word in "${_gfccc_command_string[@]}"; do + _gfccc_command+=("${command_word}") + done + echo "${_gfccc_command[@]}" + fi +} + +getFirstContainerComponentArgs() { + devfilePath=$1 + local _gfcca_args=() + local _gfcca_args_string=() + + IFS=" " read -r -a _gfcca_args_string <<< "$($YQ_PATH eval '[ .components[] | select(has("container")) ] | .[0].container.args[]? + " "' "$devfilePath" -r | paste -s -d '\0' -)" + if (( ${#_gfcca_args_string[@]} == 0 )); then + echo "" + else + for arg in "${_gfcca_args_string[@]}"; do + _gfcca_args+=("${arg}") + done + echo "${_gfcca_args[@]}" + fi +} + +isNonTerminating() { + _int_image=$1 + _int_command=("$2") + _int_command_args=("$3") + + timeout_in_sec=60 # <== includes image pulling + + echo " PARAMS: image --> $_int_image, command --> ${_int_command[*]}, args --> ${_int_command_args[*]}" + + if [ "${_int_command[*]}" == "null" ] && [ "${_int_command_args[*]}" == "null" ]; then + echo " COMMAND: \"kubectl run test-terminating -n ${TEST_NAMESPACE} --attach=false --restart=Never --image=$_int_image\"" + kubectl run test-terminating -n "${TEST_NAMESPACE}" --attach=false --restart=Never --image="$_int_image" >/dev/null 2>&1 + elif [ "${_int_command[*]}" == "null" ]; then + echo " COMMAND: \"kubectl run test-terminating -n ${TEST_NAMESPACE} --attach=false --restart=Never --image=$_int_image -- ${_int_command_args[*]}\"" + kubectl run test-terminating -n "${TEST_NAMESPACE}" --attach=false --restart=Never --image="$_int_image" -- ${_int_command_args[*]} >/dev/null 2>&1 + elif [ "${_int_command_args[*]}" == "null" ]; then + echo " COMMAND: \"kubectl run test-terminating -n ${TEST_NAMESPACE} --attach=false --restart=Never --image=$_int_image --command -- ${_int_command[*]}\"" + kubectl run test-terminating -n "${TEST_NAMESPACE}" --attach=false --restart=Never --image="$_int_image" --command=true -- ${_int_command[*]} >/dev/null 2>&1 + else + echo " COMMAND: \"kubectl run test-terminating -n ${TEST_NAMESPACE} --attach=false --restart=Never --image=$_int_image --command -- ${_int_command[*]} ${_int_command_args[*]}\"" + kubectl run test-terminating -n "${TEST_NAMESPACE}" --attach=false --restart=Never --image="$_int_image" --command=true -- ${_int_command[*]} ${_int_command_args[*]} >/dev/null 2>&1 + fi + + if kubectl wait pods -n "${TEST_NAMESPACE}" test-terminating --for condition=Ready --timeout=${timeout_in_sec}s >/dev/null 2>&1; then + echo " SUCCESS: The container started successfully and didn't terminate" + kubectl delete pod test-terminating -n "${TEST_NAMESPACE}" >/dev/null 2>&1 + return 0 + else + echo " ERROR: Failed to reach \"Ready\" condition after $timeout_in_sec seconds" + echo " ↓↓↓↓↓↓↓↓↓ Pod description ↓↓↓↓↓↓↓↓" + echo "" + kubectl describe pod -n "${TEST_NAMESPACE}" test-terminating + echo "" + echo " ↑↑↑↑↑↑↑↑↑ Pod description ↑↑↑↑↑↑↑↑" + kubectl delete pod test-terminating -n "${TEST_NAMESPACE}" >/dev/null 2>&1 + return 1 + fi +} + +YQ_PATH=${YQ_PATH:-yq} +TEST_NAMESPACE=${TEST_NAMESPACE:-default} + +find "$DEVFILES_DIR" -maxdepth 1 -type d ! -path "$DEVFILES_DIR" -print0 | while IFS= read -r -d '' devfile_dir; do + + devfile_path=$devfile_dir/devfile.yaml + + echo "=======================" + echo "Testing ${devfile_path}" + + IFS=" " read -r -a components_num <<< "$(getContainerComponentsNum "$devfile_path")" + # components_num=($(getContainerComponentsNum "$devfile_path")) + + # if there are zero components of type container skip + if (( components_num = 0 )); then + echo "WARNING: Devfile with no container component found (""$devfile_path""). Skipping." + echo "=======================" + continue + fi + + # if there is more than one component of type container skip (we may want to cover this case in the future) + if (( components_num > 1 )); then + echo "WARNING: Devfile with more than one container component found (""$devfile_path""). Skipping." + echo "=======================" + continue + fi + + name=$(getName "$devfile_path") + image=$(getFirstContainerComponentImage "$devfile_path") + + declare -a command=() + IFS=" " read -r -a command <<< "$(getFirstContainerComponentCommand "$devfile_path")" + + declare -a command_args=() + IFS=" " read -r -a command_args <<< "$(getFirstContainerComponentArgs "$devfile_path")" + + if (( ${#command[@]} > 0 )); then + command_string="${command[*]}" + else + command_string="null" + fi + + if (( ${#command_args[@]} > 0 )); then + command_args_string="${command_args[*]}" + else + command_args_string="null" + fi + + isNonTerminating "${image}" "${command_string}" "${command_args_string}"; + + echo "=======================" +done + +exit 0 diff --git a/tests/test.sh b/tests/check_odo_happy_path.sh similarity index 100% rename from tests/test.sh rename to tests/check_odo_happy_path.sh