From 68ce767ab333aa7918dc4eb7c55e16c1622f3f0f Mon Sep 17 00:00:00 2001 From: Stephane Bouchet Date: Wed, 20 May 2026 12:11:07 +0200 Subject: [PATCH 1/9] fix java-quarkus devfile sample. update README to match current code Sync documentation with actual implementation: unified validation via LANDING_PAGE_PORT, all timeouts now 120s, PROJECTS placeholder and starterProjects handling, updated container resolution logic, corrected file descriptions. Co-Authored-By: Claude Opus 4.6 Signed-off-by: Stephane Bouchet --- README.md | 86 +++++++++++++------------------ devfiles/devfiles-full.txt | 12 ++--- devworkspace-template.yaml | 6 +-- dw-auto-validate.sh | 90 +++++++++++++++++++++++++++++---- images/images-full.txt | 51 +++++++++++++++++++ samples/samples-full.txt | 12 ++++- samples/samples.txt | 2 +- settings/settings-jetbrains.env | 43 +--------------- settings/settings-sshd.env | 24 +-------- settings/settings-vscode.env | 25 +-------- verify_images.sh | 3 +- 11 files changed, 193 insertions(+), 161 deletions(-) diff --git a/README.md b/README.md index e4a6060..4cd83a4 100644 --- a/README.md +++ b/README.md @@ -7,13 +7,13 @@ Automated validation tool for testing DevWorkspace instances on OpenShift cluste ### Running Tests ```bash -# Basic validation (uses small numbers of entries - 3 images, 5 devfiles) +# Basic validation (uses small lists - images/images.txt and devfiles/devfiles.txt) ./dw-auto-validate.sh # Verbose mode - shows detailed output ./dw-auto-validate.sh -v -# Full test matrix (uses all entries from -full.txt files - all images, all devfiles. Takes significant time to complete!) +# Full test matrix (uses *-full.txt files - all images, all devfiles. Takes significant time to complete!) ./dw-auto-validate.sh -f # Debug mode - verbose + runs only first test + no cleanup @@ -81,38 +81,32 @@ Each scenario in `settings/settings-.env` exports: - `PROJECT_URL`: Git repository URL (must include surrounding double quotes) - `EDITOR_DEFINITION`: URL to the editor definition YAML - `EDITOR_COMPONENT_NAME`: Component name in the editor definition that contains the editor image (used by `-i`/`-p` to replace the correct image) -- `validate_devworkspace()`: Function that validates the running DevWorkspace +- `LANDING_PAGE_PORT`: Port to curl inside the pod to validate the editor is running -#### Scenario Validation Methods +#### Scenario Validation -**sshd** (settings-sshd.env): -- Timeout: 60s -- Checks `/tmp/sshd.log` for "Server listening on" -- Verifies SSHD server started successfully +All scenarios use the same validation method: curl `localhost:${LANDING_PAGE_PORT}` inside the pod and check for HTTP 200. -**jetbrains** (settings-jetbrains.env): -- Timeout: 120s -- Port-forwards to 3400, curls `127.0.0.1:3400` -- Validates HTTP 200 response from JetBrains landing page -- On failure, outputs `/idea-server/std.out` for debugging - -**vscode** (settings-vscode.env): -- Timeout: 60s -- Checks `/checode/entrypoint-logs.txt` for "Extension host agent listening on 3100" -- Verifies VSCode extension host is listening +| Scenario | Timeout | Landing Page Port | +|----------|---------|-------------------| +| sshd | 60s | 3400 | +| jetbrains | 120s | 3400 | +| vscode | 60s | 3100 | ### DevWorkspace Generation Uses `devworkspace-template.yaml` as base, performs sed substitutions in two stages: -**Stage 1** - Metadata and devfile injection: +**Stage 1** - Metadata, devfile, and projects injection: ```bash cat devworkspace-template.yaml | sed \ - -e "/DEVFILE/r ${TMP_DEVFILE}" \ # Inject devfile content - -e '/DEVFILE/ d' \ # Remove DEVFILE placeholder + -e "/DEVFILE/r ${TMP_DEVFILE}" \ # Inject devfile content + -e '/DEVFILE/ d' \ # Remove DEVFILE placeholder + -e "/PROJECTS/r ${TMP_PROJECTS}" \ # Inject projects block + -e '/PROJECTS/ d' \ # Remove PROJECTS placeholder -e "s|DEVWORKSPACE_NAME|...|" \ -e "s|DEVWORKSPACE_NS|...|" \ - -e "s|EDITOR_DEFINITION|...|" \ + -e "${EDITOR_SED_EXPR}" \ # Editor definition (uri or kubernetes ref) -e "s|PROJECT_URL|...|" ``` @@ -123,6 +117,10 @@ eval "sed \"s|image: .*|image: ${image}|\" > ${TMP_DEVWORKSPACE}" The two-stage approach ensures devfile content is injected before image replacement. +**Projects handling**: If the devfile contains `starterProjects`, those are extracted and converted into a `projects:` block. Otherwise, the scenario's `PROJECT_URL` is used as a fallback sample project. + +**Editor contribution**: When using `-p` or `-i` (override image), the editor contribution switches from `uri:` to `kubernetes: name:` referencing the applied DevWorkspaceTemplate. + ### Logging and Output Control - `log()`: Outputs only when `VERBOSE=1` (set by `-v` or `-d` flags) @@ -140,16 +138,16 @@ Tracks test execution time using bash's `$SECONDS` variable: ``` settings/ - settings-sshd.env # SSHD scenario: timeout=60s, validates /tmp/sshd.log - settings-jetbrains.env # JetBrains scenario: timeout=120s, validates port 3400 - settings-vscode.env # VSCode scenario: timeout=60s, validates /checode/entrypoint-logs.txt + settings-sshd.env # SSHD scenario: timeout=60s, port 3400 + settings-jetbrains.env # JetBrains scenario: timeout=120s, port 3400 + settings-vscode.env # VSCode scenario: timeout=60s, port 3100 images/ images.txt # Quick test list (3 UDI images: ubi8, ubi9, ubi10) - images-full.txt # Complete test matrix (UDI + base-developer-image variants) + images-full.txt # Complete test matrix (UDI, base-developer-image, and UBI variants) devfiles/ - devfiles.txt # Quick test list (nodejs, go, python, php-laravel and java-quarkus devfile) + devfiles.txt # Quick test list (nodejs, go, php-laravel, python) devfiles-full.txt # Complete devfile list (32 devfiles from devfile registry) samples/ @@ -163,28 +161,15 @@ verify_images.sh # Skopeo-based image accessibility checker ## Implementation Details -### Validation Function Pattern - -All `validate_devworkspace()` functions follow this pattern: +### Validation Function -```bash -validate_devworkspace() { - devfile_url=$1 # Receives devfile URL as first argument - - # Resolve pod and container via shared helper - resolve_devworkspace_pod || return 1 - - # Scenario-specific validation logic here - # Return 0 for pass, 1 for fail -} -``` +A single `validate_devworkspace()` function in the main script handles all scenarios: -The shared `resolve_devworkspace_pod()` function sets `podName` and `mainContainerName` globals. +1. Calls `resolve_devworkspace_pod()` to set `podName` and `mainContainerName` globals +2. Curls `localhost:${LANDING_PAGE_PORT}` inside the pod container +3. Returns 0 if HTTP 200, 1 otherwise -**Critical details**: -- Has access to `${DEVWORKSPACE_NS}`, `${DEVWORKSPACE_NAME}`, `log()` -- Must return 0 for success, 1 for failure -- Should use `&>/dev/null` on oc exec commands meant only for exit code checking +`resolve_devworkspace_pod()` finds the pod by DevWorkspace label and selects the main container from pod status, filtering out containers whose name starts with `che-`. ### Variable Quoting Requirements @@ -216,14 +201,13 @@ podNameAndDWName=$(oc get pods -o 'jsonpath={range .items[*]}{.metadata.name}{", podName=$(echo ${podNameAndDWName} | grep ${DEVWORKSPACE_NAME} | cut -d, -f1) ``` -**Getting main container name**: +**Getting main container name** (from pod status, excluding `che-*` containers): ```bash -mainContainerName=$(oc get devworkspace ${DEVWORKSPACE_NAME} -o json | jq -r '[.spec.template.components[] | select(.container) | .name] | first') +mainContainerName=$(oc get pod "${podName}" -o json | jq -r '[.status.containerStatuses[] | select(.state.running and (.name | test("^che-") | not))] | first | .name // empty') ``` ### Adding a New Scenario 1. Create `settings/settings-.env` -2. Export required variables: `TIMEOUT`, `DEVWORKSPACE_NAME`, `PROJECT_URL`, `EDITOR_DEFINITION` -3. Implement `validate_devworkspace()` function that returns 0/1 -4. Update scenario selection in dw-auto-validate.sh (add option, update prompts) +2. Export required variables: `TIMEOUT`, `DEVWORKSPACE_NAME`, `PROJECT_URL`, `EDITOR_DEFINITION`, `EDITOR_COMPONENT_NAME`, `LANDING_PAGE_PORT` +3. Update scenario selection in dw-auto-validate.sh (add option, update prompts and `-s` validation) diff --git a/devfiles/devfiles-full.txt b/devfiles/devfiles-full.txt index cdd242b..98f185d 100644 --- a/devfiles/devfiles-full.txt +++ b/devfiles/devfiles-full.txt @@ -1,5 +1,6 @@ https://registry.devfile.io/devfiles/dotnet80 https://registry.devfile.io/devfiles/go +https://registry.devfile.io/devfiles/code-with-quarkus https://registry.devfile.io/devfiles/java-maven https://registry.devfile.io/devfiles/java-openliberty https://registry.devfile.io/devfiles/java-openliberty-gradle @@ -20,13 +21,12 @@ https://registry.devfile.io/devfiles/nodejs-nuxtjs https://registry.devfile.io/devfiles/nodejs-react https://registry.devfile.io/devfiles/nodejs-svelte https://registry.devfile.io/devfiles/nodejs-vue -https://registry.devfile.io/devfiles/ollama https://registry.devfile.io/devfiles/php-laravel https://registry.devfile.io/devfiles/python https://registry.devfile.io/devfiles/python-django https://registry.devfile.io/devfiles/udi -https://registry.devfile.io/devfiles/nodejs-basic -https://registry.devfile.io/devfiles/code-with-quarkus -https://registry.devfile.io/devfiles/java-springboot-basic -https://registry.devfile.io/devfiles/python-basic -https://registry.devfile.io/devfiles/go-basic +https://registry.devfile.io/devfiles/hermes +https://registry.devfile.io/devfiles/ollama +https://registry.devfile.io/devfiles/openclaw +https://registry.devfile.io/devfiles/picoclaw +https://registry.devfile.io/devfiles/zeroclaw diff --git a/devworkspace-template.yaml b/devworkspace-template.yaml index 4e15175..9f97591 100644 --- a/devworkspace-template.yaml +++ b/devworkspace-template.yaml @@ -8,11 +8,7 @@ spec: routingClass: che # thanks claude template: DEVFILE - projects: - - name: project-sample - git: - remotes: - origin: PROJECT_URL + PROJECTS contributions: - name: editor-definition-component diff --git a/dw-auto-validate.sh b/dw-auto-validate.sh index e8d8ba3..a186f20 100755 --- a/dw-auto-validate.sh +++ b/dw-auto-validate.sh @@ -100,7 +100,7 @@ resolve_devworkspace_pod() { log "${YELLOW}podNameAndDWName: \n${NC}${podNameAndDWName}" podName=$(echo "${podNameAndDWName}" | grep ${DEVWORKSPACE_NAME} | cut -d, -f1) log "${YELLOW}podName: \n${NC}${podName}" - mainContainerName=$(oc get devworkspace ${DEVWORKSPACE_NAME} -o json | jq -r '[.spec.template.components[] | select(.container) | .name] | first') + mainContainerName=$(oc get pod "${podName}" -o json | jq -r '[.status.containerStatuses[] | select(.state.running and (.name | test("^che-") | not))] | first | .name // empty') log "${YELLOW}mainContainerName: \n${NC}${mainContainerName}" if [ -z "${podName}" ] || [ -z "${mainContainerName}" ]; then log "Could not find pod/container matching ${DEVWORKSPACE_NAME}" @@ -110,6 +110,20 @@ resolve_devworkspace_pod() { return 0 } +validate_devworkspace() { + resolve_devworkspace_pod || return 1 + + log "Checking editor on localhost:${LANDING_PAGE_PORT}" + http_code=$(oc exec -n ${DEVWORKSPACE_NS} ${podName} -c ${mainContainerName} -- curl -s -o /dev/null -w '%{http_code}' http://localhost:${LANDING_PAGE_PORT}) + if [ "${http_code}" == "200" ]; then + log "${GREEN}localhost:${VALIDATION_PORT} returned HTTP ${http_code}${NC}" + return 0 + else + log "${YELLOW}localhost:${VALIDATION_PORT} returned HTTP ${http_code}${NC}" + return 1 + fi +} + shouldExclude() { for imagePattern in ${EXCLUDED_IMAGE_PATTERNS[@]}; do if [[ ${1} =~ ${imagePattern} ]]; then @@ -319,8 +333,33 @@ fi echo -e "${BLUE}There will be ${total_tests} tests performed in total.${NC}" for devfile_url in "${DEVFILE_URL_LIST[@]}"; do - curl -sL -o ${TMP_DEVFILE} ${devfile_url} - sed -i.tmp 's/^/ /' ${TMP_DEVFILE} && rm -f "${TMP_DEVFILE}.tmp" + http_code=$(curl -sL -o ${TMP_DEVFILE} -w '%{http_code}' ${devfile_url}) + if [ "${http_code}" != "200" ]; then + echo "${devfile_url} — fetch failed (HTTP ${http_code}), skipping this devfile. Numbers might not be accurate. ❌" + failed_test+=("Devfile '${devfile_url}' — fetch failed (HTTP ${http_code})") + continue + fi + + # Build the projects block: use the devfile's starterProjects if present, + # otherwise fall back to the default sample project. + # Must check before indenting the devfile. + TMP_PROJECTS=$(mktemp -t projects-XXX.yaml) + if grep -q '^starterProjects:' ${TMP_DEVFILE}; then + sed -n '/^starterProjects:/,/^[a-zA-Z]/{/^starterProjects:/p; /^ /p}' ${TMP_DEVFILE} | \ + sed 's/^starterProjects:/projects:/' | \ + sed 's/^/ /' > ${TMP_PROJECTS} + + else + cat > ${TMP_PROJECTS} <<'PROJEOF' + projects: + - name: project-sample + git: + remotes: + origin: PROJECT_URL +PROJEOF + fi + + sed -i.tmp 's/^/ /' ${TMP_DEVFILE} && rm -f "${TMP_DEVFILE}.tmp" for image in "${IMAGES_LIST[@]}"; do #debug mode: stop after one iteration @@ -346,6 +385,8 @@ for devfile_url in "${DEVFILE_URL_LIST[@]}"; do sed \ -e "/DEVFILE/r ${TMP_DEVFILE}" \ -e '/DEVFILE/ d' \ + -e "/PROJECTS/r ${TMP_PROJECTS}" \ + -e '/PROJECTS/ d' \ -e "s|DEVWORKSPACE_NAME|${DEVWORKSPACE_NAME}|" \ -e "s|DEVWORKSPACE_NS|${DEVWORKSPACE_NS}|" \ -e "${EDITOR_SED_EXPR}" \ @@ -353,19 +394,40 @@ for devfile_url in "${DEVFILE_URL_LIST[@]}"; do # Modify the result (must be separate) # here is the replacement of the container image used in the devfile from an image in the list eval "sed \"s|image: .*|image: ${image}|\" > ${TMP_DEVWORKSPACE}" + # Stop the DevWorkspace before applying to force a pod restart + current_phase=$(oc get dw ${DEVWORKSPACE_NAME} -o 'jsonpath={.status.phase}' 2>/dev/null) + if [ "${current_phase}" == "Running" ] || [ "${current_phase}" == "Starting" ] || [ "${current_phase}" == "Failed" ]; then + eval "oc patch dw ${DEVWORKSPACE_NAME} --type merge -p '{\"spec\":{\"started\":false}}' ${QUIET}" + log -n "Stopping ${DEVWORKSPACE_NAME} ." + stop_count=0 + stop_timeout=$((TIMEOUT / 4)) + while [ "$(oc get dw ${DEVWORKSPACE_NAME} -o 'jsonpath={.status.phase}' 2>/dev/null)" != "Stopped" ] && [ ${stop_count} -lt ${stop_timeout} ]; do + sleep 1s + log -n "." + stop_count=$((stop_count+1)) + done + if [ ${stop_count} -ge ${stop_timeout} ]; then + log "\n${YELLOW}${DEVWORKSPACE_NAME} failed to stop (timed out after ${stop_timeout}s)${NC}" + echo "TEST [${total_count}/${total_tests}] ${devfile_url} with ${image} FAILED ❌" + failed_test+=("Devfile '$devfile_url' using image '$image'") + continue + fi + log " stopped." + fi eval "oc apply -f ${TMP_DEVWORKSPACE} ${QUIET}" state="" - log -n "Waiting for ${DEVWORKSPACE_NAME} .." + log -n "Waiting for ${DEVWORKSPACE_NAME} to run ." count=0 - while [ "${state}" != "Running" ] && [ ${count} -lt ${TIMEOUT} ]; do + while [ "${state}" != "Running" ] && [ "${state}" != "Failed" ] && [ ${count} -lt ${TIMEOUT} ]; do state=$(oc get dw ${DEVWORKSPACE_NAME} -o 'jsonpath={.status.phase}') sleep 1s log -n "." count=$((count+1)) done - if [ ${state} == "Running" ]; then - log "\n${GREEN}${DEVWORKSPACE_NAME} is Running${NC}" + if [ "${state}" == "Running" ]; then + log "\n${GREEN}${DEVWORKSPACE_NAME} is running.${NC}" else +<<<<<<< Upstream, based on main log "\n${YELLOW}${DEVWORKSPACE_NAME} failed to start${NC}" if shouldExclude ${image}; then echo "TEST [${total_count}/${total_tests}] ${devfile_url} with ${image} FAILED ❌ (EXCLUDED ↩️ )" @@ -374,10 +436,19 @@ for devfile_url in "${DEVFILE_URL_LIST[@]}"; do echo "TEST [${total_count}/${total_tests}] ${devfile_url} with ${image} FAILED ❌" failed_test+=("Devfile '$devfile_url' using image '$image'") fi +======= + if [ "${state}" == "Failed" ]; then + log "\n${YELLOW}${DEVWORKSPACE_NAME} failed to start (state: Failed after ${count}s)${NC}" + else + log "\n${YELLOW}${DEVWORKSPACE_NAME} failed to start (timed out after ${TIMEOUT}s, last state: ${state})${NC}" + fi + echo "TEST [${total_count}/${total_tests}] ${devfile_url} with ${image} FAILED ❌" + failed_test+=("Devfile '$devfile_url' using image '$image'") +>>>>>>> 7fab193 fix java-quarkus devfile sample. continue fi log "Validating ${DEVWORKSPACE_NAME} .." - validate_devworkspace ${devfile_url} + validate_devworkspace if [ $? -eq 0 ]; then echo "TEST [${total_count}/${total_tests}] ${devfile_url} with ${image} PASSED ✅" ((success_count++)) @@ -392,7 +463,7 @@ for devfile_url in "${DEVFILE_URL_LIST[@]}"; do fi sleep 1s done # image loop - + [[ ${DEBUG} -eq 1 && ${total_count} -ge 1 ]] && break done # devfile loop # cleanup @@ -405,6 +476,7 @@ cleanup() { sleep 1s rm $TMP_DEVFILE + rm $TMP_PROJECTS rm $TMP_DEVWORKSPACE if [ -n "${OVERRIDE_IMAGE}" ]; then rm $TMP_EDITOR_DEF diff --git a/images/images-full.txt b/images/images-full.txt index 3c6bd0d..25e967e 100644 --- a/images/images-full.txt +++ b/images/images-full.txt @@ -133,6 +133,49 @@ registry.access.redhat.com/ubi9/toolbox:9.7 registry.access.redhat.com/ubi9/s2i-base:9.7 registry.access.redhat.com/ubi9/s2i-core:9.7 +registry.access.redhat.com/ubi9:9.8 +registry.access.redhat.com/ubi9-minimal:9.8 +registry.access.redhat.com/ubi9-micro:9.8 +registry.access.redhat.com/ubi9-init:9.8 +registry.access.redhat.com/ubi9/ubi-stig:9.8 +registry.access.redhat.com/ubi9/python-39:9.8 +registry.access.redhat.com/ubi9/python-311:9.8 +registry.access.redhat.com/ubi9/python-312:9.8 +registry.access.redhat.com/ubi9/python-312-minimal:9.8 +registry.access.redhat.com/ubi9/python-314:9.8 +registry.access.redhat.com/ubi9/python-314-minimal:9.8 +registry.access.redhat.com/ubi9/nodejs-22:9.8 +registry.access.redhat.com/ubi9/nodejs-22-minimal:9.8 +registry.access.redhat.com/ubi9/nodejs-24:9.8 +registry.access.redhat.com/ubi9/nodejs-24-minimal:9.8 +registry.access.redhat.com/ubi9/ruby-30:9.8 +registry.access.redhat.com/ubi9/ruby-33:9.8 +registry.access.redhat.com/ubi9/ruby-40:9.8 +registry.access.redhat.com/ubi9/php-80:9.8 +registry.access.redhat.com/ubi9/php-82:9.8 +registry.access.redhat.com/ubi9/php-83:9.8 +registry.access.redhat.com/ubi9/perl-532:9.8 +registry.access.redhat.com/ubi9/openjdk-17:1.24 +registry.access.redhat.com/ubi9/openjdk-17-runtime:1.24 +registry.access.redhat.com/ubi9/openjdk-21:1.24 +registry.access.redhat.com/ubi9/openjdk-21-runtime:1.24 +registry.access.redhat.com/ubi9/openjdk-25:1.24 +registry.access.redhat.com/ubi9/openjdk-25-runtime:1.24 +registry.access.redhat.com/ubi9/dotnet-100:9.8 +registry.access.redhat.com/ubi9/dotnet-100-runtime:9.8 +registry.access.redhat.com/ubi9/dotnet-100-aspnet:9.8 +registry.access.redhat.com/ubi9/go-toolset:9.8 +registry.access.redhat.com/ubi9/nginx-120:9.8 +registry.access.redhat.com/ubi9/nginx-124:9.8 +registry.access.redhat.com/ubi9/nginx-126:9.8 +registry.access.redhat.com/ubi9/httpd-24:9.8 +registry.access.redhat.com/ubi9/buildah:9.8 +registry.access.redhat.com/ubi9/skopeo:9.8 +registry.access.redhat.com/ubi9/podman:9.8 +registry.access.redhat.com/ubi9/toolbox:9.8 +registry.access.redhat.com/ubi9/s2i-base:9.8 +registry.access.redhat.com/ubi9/s2i-core:9.8 + registry.access.redhat.com/ubi10:10.0 registry.access.redhat.com/ubi10/ubi-minimal:10.0 registry.access.redhat.com/ubi10/ubi-micro:10.0 @@ -150,6 +193,10 @@ registry.access.redhat.com/ubi10/buildah:10.0 registry.access.redhat.com/ubi10/toolbox:10.0 registry.access.redhat.com/ubi10/podman:10.0 registry.access.redhat.com/ubi10/skopeo:10.0 +registry.access.redhat.com/ubi10/openjdk-21:1.24 +registry.access.redhat.com/ubi10/openjdk-21-runtime:1.24 +registry.access.redhat.com/ubi10/openjdk-25:1.24 +registry.access.redhat.com/ubi10/openjdk-25-runtime:1.24 registry.access.redhat.com/ubi10/s2i-base:10.0 registry.access.redhat.com/ubi10/s2i-core:10.0 @@ -172,5 +219,9 @@ registry.access.redhat.com/ubi10/buildah:10.1 registry.access.redhat.com/ubi10/toolbox:10.1 registry.access.redhat.com/ubi10/podman:10.1 registry.access.redhat.com/ubi10/skopeo:10.1 +registry.access.redhat.com/ubi10/openjdk-21:1.24 +registry.access.redhat.com/ubi10/openjdk-21-runtime:1.24 +registry.access.redhat.com/ubi10/openjdk-25:1.24 +registry.access.redhat.com/ubi10/openjdk-25-runtime:1.24 registry.access.redhat.com/ubi10/s2i-base:10.1 registry.access.redhat.com/ubi10/s2i-core:10.1 diff --git a/samples/samples-full.txt b/samples/samples-full.txt index 8d13ecf..9d94786 100644 --- a/samples/samples-full.txt +++ b/samples/samples-full.txt @@ -1,5 +1,3 @@ -https://github.com/che-samples/web-nodejs-sample -https://github.com/che-samples/nodejs-mongodb-sample https://github.com/devfile-samples/devfile-stack-go https://github.com/devfile-samples/devfile-stack-nodejs-angular https://github.com/devfile-samples/devfile-stack-nodejs-nextjs @@ -8,6 +6,15 @@ https://github.com/devfile-samples/devfile-stack-nodejs-svelte https://github.com/devfile-samples/devfile-stack-nodejs-vue https://github.com/devfile-samples/devfile-stack-php-laravel https://github.com/devfile-samples/devfile-stacks-nodejs-react +https://github.com/devfile-samples/devfile-sample-code-with-quarkus +https://github.com/devfile-samples/devfile-sample-dotnet60-basic +https://github.com/devfile-samples/devfile-sample-go-basic +https://github.com/devfile-samples/devfile-sample-java-springboot-basic +https://github.com/devfile-samples/devfile-sample-python-basic +https://github.com/devfile-samples/node-express-hello-devfile-no-dockerfile +https://github.com/devfile-samples/nodejs-ex +https://github.com/devfile-samples/nodejs-mongodb-sample +https://github.com/devfile-samples/web-coolstore https://github.com/devfile-samples/python-django-ex https://github.com/devfile-samples/python-ex https://github.com/devfile-samples/springboot-ex @@ -37,3 +44,4 @@ https://github.com/wildfly-extras/wildfly-devfile-examples https://github.com/wildfly/quickstart https://github.com/RedHat-Middleware-Workshops/devsandbox-camel https://github.com/che-incubator/quarkus-api-example + diff --git a/samples/samples.txt b/samples/samples.txt index 12c09b5..5896abf 100644 --- a/samples/samples.txt +++ b/samples/samples.txt @@ -1 +1 @@ -https://github.com/che-samples/web-nodejs-sample \ No newline at end of file +https://github.com/devfile-samples/nodejs-ex \ No newline at end of file diff --git a/settings/settings-jetbrains.env b/settings/settings-jetbrains.env index a3e8c17..5b7181a 100644 --- a/settings/settings-jetbrains.env +++ b/settings/settings-jetbrains.env @@ -18,44 +18,5 @@ export EDITOR_COMPONENT_NAME='editor-injector' # array of image patterns (Posix Extended Regular Expressions) excluded from failed tests export EXCLUDED_IMAGE_PATTERNS=('ubi[0-9]{0,2}-micro' 'ubi[0-9]{0,2}-stig' 'ubi[0-9]{0,2}/pause') -# Function that evaluates whether DevWorkspace is valid -validate_devworkspace() { - devfile_url=$1 - LANDING_PAGE_PORT=3400 - - resolve_devworkspace_pod || return 1 - - log "Waiting for response .." - # Access the landing page - if [ ${VERBOSE} -eq 0 ]; then - oc port-forward -n ${DEVWORKSPACE_NS} ${podName} ${LANDING_PAGE_PORT}:${LANDING_PAGE_PORT} &>/dev/null & - else - oc port-forward -n ${DEVWORKSPACE_NS} ${podName} ${LANDING_PAGE_PORT}:${LANDING_PAGE_PORT} & - fi - PORT_FORWARD_PID=$! - - response="000" - count=0 - while [ "${response}" != "200" ] && [ ${count} -lt ${TIMEOUT} ]; do - sleep 1s - response=$(curl -s -o /dev/null -w "%{http_code}" "127.0.0.1:${LANDING_PAGE_PORT}") - count=$((count+1)) - [ "${response}" != "000" ] && break - log -n "." - done - log "response: ${response}" - - # Terminate oc-port-forward - kill ${PORT_FORWARD_PID} 2>/dev/null - wait ${PORT_FORWARD_PID} 2>/dev/null - sleep 1s - - if [ "${response}" == "200" ]; then - # pass - return 0 - else - # fail - log $(oc exec -n ${DEVWORKSPACE_NS} ${podName} -c ${mainContainerName} -- cat /idea-server/std.out) - return 1 - fi -} +# port to check for editor readiness +export LANDING_PAGE_PORT=3400 diff --git a/settings/settings-sshd.env b/settings/settings-sshd.env index 602af49..d27c69d 100644 --- a/settings/settings-sshd.env +++ b/settings/settings-sshd.env @@ -18,25 +18,5 @@ export EDITOR_COMPONENT_NAME='che-code-sshd-page' # array of image patterns (Posix Extended Regular Expressions) excluded from failed tests export EXCLUDED_IMAGE_PATTERNS=('ubi[0-9]{0,2}-micro' 'ubi[0-9]{0,2}-stig' 'ubi[0-9]{0,2}/pause') -# Function that evaluates whether DevWorkspace is valid -validate_devworkspace() { - devfile_url=$1 - - resolve_devworkspace_pod || return 1 - - oc exec -n ${DEVWORKSPACE_NS} ${podName} -c ${mainContainerName} -- cat /tmp/sshd.log &>/dev/null #always quiet this message an rely on return code - if [ $? -eq 1 ]; then - # fail to cat ssh log file - log "Failed to cat /tmp/sshd.log file on container" - return 1 - fi - res=$(oc exec -n ${DEVWORKSPACE_NS} ${podName} -c ${mainContainerName} -- cat /tmp/sshd.log | grep -q 'Server listening on') - if [ $? -eq 0 ]; then - # pass - return 0 - else - # fail - log "Failed to retrieve sucessfull sshd status log in /tmp/sshd.log file on container" - return 1 - fi -} +# port to check for editor readiness +export LANDING_PAGE_PORT=3400 diff --git a/settings/settings-vscode.env b/settings/settings-vscode.env index 9b07d16..01300b1 100644 --- a/settings/settings-vscode.env +++ b/settings/settings-vscode.env @@ -18,26 +18,5 @@ export EDITOR_COMPONENT_NAME='che-code-injector' # array of image patterns (Posix Extended Regular Expressions) excluded from failed tests export EXCLUDED_IMAGE_PATTERNS=('ubi[0-9]{0,2}-micro' 'ubi[0-9]{0,2}-stig' 'ubi[0-9]{0,2}/pause') -# Function that evaluates whether DevWorkspace is valid -validate_devworkspace() { - devfile_url=$1 - - resolve_devworkspace_pod || return 1 - - oc exec -n ${DEVWORKSPACE_NS} ${podName} -c ${mainContainerName} -- cat /checode/entrypoint-logs.txt &>/dev/null #always quiet this message an rely on return code - if [ $? -eq 1 ]; then - # fail to cat vscode log file - log "Failed to cat /checode/entrypoint-logs.txt file on container" - return 1 - fi - res=$(oc exec -n ${DEVWORKSPACE_NS} ${podName} -c ${mainContainerName} -- cat /checode/entrypoint-logs.txt | grep -q 'Extension host agent listening on 3100') - if [ $? -eq 0 ]; then - # pass - return 0 - else - # fail - log "Failed to retrieve sucessfull vscode status log in /checode/entrypoint-logs.txt file on container" - log $(oc exec -n ${DEVWORKSPACE_NS} ${podName} -c ${mainContainerName} -- cat /checode/entrypoint-logs.txt) - return 1 - fi -} +# port to check for editor readiness +export LANDING_PAGE_PORT=3100 diff --git a/verify_images.sh b/verify_images.sh index 019fc9b..c485b13 100755 --- a/verify_images.sh +++ b/verify_images.sh @@ -2,13 +2,14 @@ failed_images=() success_count=0 total_count=0 +image_count=$(grep -c '[^[:space:]]' images/images-full.txt) while IFS= read -r image; do # Skip empty lines [[ -z "$image" ]] && continue ((total_count++)) - echo "Checking [$total_count]: $image" + echo "Checking [$total_count/$image_count]: $image" if skopeo inspect --no-tags --retry-times 2 "docker://$image" > /dev/null 2>&1; then ((success_count++)) From 5dab1ba2353154b53320ae6eea1f44b539f12c98 Mon Sep 17 00:00:00 2001 From: Stephane Bouchet Date: Wed, 20 May 2026 12:11:07 +0200 Subject: [PATCH 2/9] fix java-quarkus devfile sample. update README to match current code Sync documentation with actual implementation: unified validation via LANDING_PAGE_PORT, all timeouts now 120s, PROJECTS placeholder and starterProjects handling, updated container resolution logic, corrected file descriptions. Co-Authored-By: Claude Opus 4.6 Signed-off-by: Stephane Bouchet --- README.md | 4 ++-- devfiles/devfiles.txt | 1 + settings/settings-vscode.env | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 4cd83a4..4c68762 100644 --- a/README.md +++ b/README.md @@ -91,7 +91,7 @@ All scenarios use the same validation method: curl `localhost:${LANDING_PAGE_POR |----------|---------|-------------------| | sshd | 60s | 3400 | | jetbrains | 120s | 3400 | -| vscode | 60s | 3100 | +| vscode | 90s | 3100 | ### DevWorkspace Generation @@ -140,7 +140,7 @@ Tracks test execution time using bash's `$SECONDS` variable: settings/ settings-sshd.env # SSHD scenario: timeout=60s, port 3400 settings-jetbrains.env # JetBrains scenario: timeout=120s, port 3400 - settings-vscode.env # VSCode scenario: timeout=60s, port 3100 + settings-vscode.env # VSCode scenario: timeout=90s, port 3100 images/ images.txt # Quick test list (3 UDI images: ubi8, ubi9, ubi10) diff --git a/devfiles/devfiles.txt b/devfiles/devfiles.txt index a5beaa0..863eeee 100644 --- a/devfiles/devfiles.txt +++ b/devfiles/devfiles.txt @@ -2,3 +2,4 @@ https://registry.devfile.io/devfiles/nodejs https://registry.devfile.io/devfiles/go https://registry.devfile.io/devfiles/php-laravel https://registry.devfile.io/devfiles/python +https://registry.devfile.io/devfiles/java-quarkus diff --git a/settings/settings-vscode.env b/settings/settings-vscode.env index 01300b1..73e2507 100644 --- a/settings/settings-vscode.env +++ b/settings/settings-vscode.env @@ -1,7 +1,7 @@ #! /bin/bash # Rough time (seconds) to wait for DevWorkspace to enter 'Running' state -export TIMEOUT=60 +export TIMEOUT=90 # name to give all created (singleton) DevWorkspace instances export DEVWORKSPACE_NAME='vscode-test' From 0a730bd9d3ee042f209e208396f4ff62b04f3b25 Mon Sep 17 00:00:00 2001 From: Stephane Bouchet Date: Thu, 13 Aug 2026 11:19:53 -0500 Subject: [PATCH 3/9] fix shell warnings Signed-off-by: Stephane Bouchet --- dw-auto-validate.sh | 39 ++++++++++++++++++++------------------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/dw-auto-validate.sh b/dw-auto-validate.sh index a186f20..394ab9d 100755 --- a/dw-auto-validate.sh +++ b/dw-auto-validate.sh @@ -114,7 +114,7 @@ validate_devworkspace() { resolve_devworkspace_pod || return 1 log "Checking editor on localhost:${LANDING_PAGE_PORT}" - http_code=$(oc exec -n ${DEVWORKSPACE_NS} ${podName} -c ${mainContainerName} -- curl -s -o /dev/null -w '%{http_code}' http://localhost:${LANDING_PAGE_PORT}) + http_code=$(oc exec -n "${DEVWORKSPACE_NS}" "${podName}" -c "${mainContainerName}" -- curl -s -o /dev/null -w '%{http_code}' http://localhost:${LANDING_PAGE_PORT}) if [ "${http_code}" == "200" ]; then log "${GREEN}localhost:${VALIDATION_PORT} returned HTTP ${http_code}${NC}" return 0 @@ -169,7 +169,7 @@ if [ -n "${PR_NUMBER}" ]; then echo -e "${GREEN}Ok!${NC}" echo -e "\n${BLUE}Checking PR image...${NC}" log "Executing 'skopeo inspect'..." - eval skopeo inspect --no-tags --retry-times 2 --override-arch amd64 --override-os linux "docker://${PR_IMAGE}" ${QUIET} + eval skopeo inspect --no-tags --retry-times 2 --override-arch amd64 --override-os linux "docker://${PR_IMAGE}" "${QUIET}" if [ $? -ne 0 ]; then echo -e "${RED}Error:${NC} PR image '${PR_IMAGE}' not found. Make sure the GitHub Action has published the image." >&2 exit 1 @@ -182,7 +182,7 @@ if [ -n "${CUSTOM_IMAGE}" ]; then if [ -x "$(command -v skopeo)" ]; then echo -e "\n${BLUE}Checking custom image...${NC}" log "Executing 'skopeo inspect'..." - eval skopeo inspect --no-tags --retry-times 2 --override-arch amd64 --override-os linux "docker://${CUSTOM_IMAGE}" ${QUIET} + eval skopeo inspect --no-tags --retry-times 2 --override-arch amd64 --override-os linux "docker://${CUSTOM_IMAGE}" "${QUIET}" if [ $? -ne 0 ]; then echo -e "${YELLOW}Warning:${NC} Could not verify custom image '${CUSTOM_IMAGE}'. Proceeding anyway." else @@ -202,11 +202,11 @@ fi echo -e "\n${BLUE}Checking cluster connection...${NC}" log "Executing 'oc whoami'..." current_cluster=$(oc config current-context) -eval oc whoami --insecure-skip-tls-verify ${QUIET} +eval oc whoami --insecure-skip-tls-verify "${QUIET}" if [ $? -eq 1 ]; then echo -e "${YELLOW}Not connected.${NC} Do you want to login to current cluster? Current cluster is ${PURPLE}${current_cluster}.${NC}" while true; do - read -p "(y/n)? : " yn + read -rp "(y/n)? : " yn case $yn in [Yy]* ) oc login --web; break;; [Nn]* ) exit;; @@ -221,7 +221,7 @@ fi if [ -z "${SCENARIO}" ]; then echo -e "\n${BLUE}Choose the dedicated scenario to run the validation test suite.${NC}\n1-sshd\n2-jetbrains\n3-vscode" while true; do - read -p "(1/2/3)? : " scenario + read -rp "(1/2/3)? : " scenario case $scenario in 1 ) SCENARIO=sshd; break;; 2 ) SCENARIO=jetbrains; break;; @@ -232,7 +232,8 @@ if [ -z "${SCENARIO}" ]; then fi # Read values from scenario's setting -. settings/settings-${SCENARIO}.env +# shellcheck source=settings/settings-vscode.env +. settings/settings-"${SCENARIO}".env # user namespace where testing will occur DEVWORKSPACE_NS=$(oc project -q) @@ -273,7 +274,7 @@ DWTEOF fi # Temporary storage for generated files -TMP_DEVFILE=$(mktemp -t devfile-${SCENARIO}-XXX.yaml) +TMP_DEVFILE=$(mktemp -t devfile-"${SCENARIO}"-XXX.yaml) TMP_DEVWORKSPACE=$(mktemp -t devworkspace-XXX.yaml) # parsing images list @@ -333,7 +334,7 @@ fi echo -e "${BLUE}There will be ${total_tests} tests performed in total.${NC}" for devfile_url in "${DEVFILE_URL_LIST[@]}"; do - http_code=$(curl -sL -o ${TMP_DEVFILE} -w '%{http_code}' ${devfile_url}) + http_code=$(curl -sL -o "${TMP_DEVFILE}" -w '%{http_code}' "${devfile_url}") if [ "${http_code}" != "200" ]; then echo "${devfile_url} — fetch failed (HTTP ${http_code}), skipping this devfile. Numbers might not be accurate. ❌" failed_test+=("Devfile '${devfile_url}' — fetch failed (HTTP ${http_code})") @@ -344,13 +345,13 @@ for devfile_url in "${DEVFILE_URL_LIST[@]}"; do # otherwise fall back to the default sample project. # Must check before indenting the devfile. TMP_PROJECTS=$(mktemp -t projects-XXX.yaml) - if grep -q '^starterProjects:' ${TMP_DEVFILE}; then - sed -n '/^starterProjects:/,/^[a-zA-Z]/{/^starterProjects:/p; /^ /p}' ${TMP_DEVFILE} | \ + if grep -q '^starterProjects:' "${TMP_DEVFILE}"; then + sed -n '/^starterProjects:/,/^[a-zA-Z]/{/^starterProjects:/p; /^ /p}' "${TMP_DEVFILE}" | \ sed 's/^starterProjects:/projects:/' | \ - sed 's/^/ /' > ${TMP_PROJECTS} + sed 's/^/ /' > "${TMP_PROJECTS}" else - cat > ${TMP_PROJECTS} <<'PROJEOF' + cat > "${TMP_PROJECTS}" <<'PROJEOF' projects: - name: project-sample git: @@ -359,7 +360,7 @@ for devfile_url in "${DEVFILE_URL_LIST[@]}"; do PROJEOF fi - sed -i.tmp 's/^/ /' ${TMP_DEVFILE} && rm -f "${TMP_DEVFILE}.tmp" + sed -i.tmp 's/^/ /' "${TMP_DEVFILE}" && rm -f "${TMP_DEVFILE}.tmp" for image in "${IMAGES_LIST[@]}"; do #debug mode: stop after one iteration @@ -475,12 +476,12 @@ cleanup() { fi sleep 1s - rm $TMP_DEVFILE - rm $TMP_PROJECTS - rm $TMP_DEVWORKSPACE + rm "$TMP_DEVFILE" + rm "$TMP_PROJECTS" + rm "$TMP_DEVWORKSPACE" if [ -n "${OVERRIDE_IMAGE}" ]; then - rm $TMP_EDITOR_DEF - rm $TMP_DWT + rm "$TMP_EDITOR_DEF" + rm "$TMP_DWT" fi } From 671b9456c109c6e95086769bfc6b3ec8f4be8739 Mon Sep 17 00:00:00 2001 From: Stephane Bouchet Date: Fri, 14 Aug 2026 16:38:25 -0500 Subject: [PATCH 4/9] uses ephemeral storage, delete after failed state Signed-off-by: Stephane Bouchet --- devworkspace-template.yaml | 2 ++ dw-auto-validate.sh | 28 +++++++++++++--------------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/devworkspace-template.yaml b/devworkspace-template.yaml index 9f97591..e389f2f 100644 --- a/devworkspace-template.yaml +++ b/devworkspace-template.yaml @@ -7,6 +7,8 @@ spec: started: true routingClass: che # thanks claude template: + attributes: + controller.devfile.io/storage-type: ephemeral DEVFILE PROJECTS diff --git a/dw-auto-validate.sh b/dw-auto-validate.sh index 394ab9d..dea7a8e 100755 --- a/dw-auto-validate.sh +++ b/dw-auto-validate.sh @@ -125,7 +125,7 @@ validate_devworkspace() { } shouldExclude() { - for imagePattern in ${EXCLUDED_IMAGE_PATTERNS[@]}; do + for imagePattern in "${EXCLUDED_IMAGE_PATTERNS[@]}"; do if [[ ${1} =~ ${imagePattern} ]]; then return 0 fi @@ -397,7 +397,11 @@ PROJEOF eval "sed \"s|image: .*|image: ${image}|\" > ${TMP_DEVWORKSPACE}" # Stop the DevWorkspace before applying to force a pod restart current_phase=$(oc get dw ${DEVWORKSPACE_NAME} -o 'jsonpath={.status.phase}' 2>/dev/null) - if [ "${current_phase}" == "Running" ] || [ "${current_phase}" == "Starting" ] || [ "${current_phase}" == "Failed" ]; then + if [ "${current_phase}" == "Failed" ]; then + log -n "Force-deleting ${DEVWORKSPACE_NAME} (Failed state) ." + eval "oc delete dw ${DEVWORKSPACE_NAME} ${QUIET}" + log " deleted." + elif [ "${current_phase}" == "Running" ] || [ "${current_phase}" == "Starting" ]; then eval "oc patch dw ${DEVWORKSPACE_NAME} --type merge -p '{\"spec\":{\"started\":false}}' ${QUIET}" log -n "Stopping ${DEVWORKSPACE_NAME} ." stop_count=0 @@ -428,24 +432,18 @@ PROJEOF if [ "${state}" == "Running" ]; then log "\n${GREEN}${DEVWORKSPACE_NAME} is running.${NC}" else -<<<<<<< Upstream, based on main - log "\n${YELLOW}${DEVWORKSPACE_NAME} failed to start${NC}" - if shouldExclude ${image}; then + if [ "${state}" == "Failed" ]; then + log "\n${YELLOW}${DEVWORKSPACE_NAME} failed to start (state: Failed after ${count}s)${NC}" + else + log "\n${YELLOW}${DEVWORKSPACE_NAME} failed to start (timed out after ${TIMEOUT}s, last state: ${state})${NC}" + fi + if shouldExclude "${image}"; then echo "TEST [${total_count}/${total_tests}] ${devfile_url} with ${image} FAILED ❌ (EXCLUDED ↩️ )" excluded_test+=("Devfile '$devfile_url' using image '$image'") else echo "TEST [${total_count}/${total_tests}] ${devfile_url} with ${image} FAILED ❌" failed_test+=("Devfile '$devfile_url' using image '$image'") fi -======= - if [ "${state}" == "Failed" ]; then - log "\n${YELLOW}${DEVWORKSPACE_NAME} failed to start (state: Failed after ${count}s)${NC}" - else - log "\n${YELLOW}${DEVWORKSPACE_NAME} failed to start (timed out after ${TIMEOUT}s, last state: ${state})${NC}" - fi - echo "TEST [${total_count}/${total_tests}] ${devfile_url} with ${image} FAILED ❌" - failed_test+=("Devfile '$devfile_url' using image '$image'") ->>>>>>> 7fab193 fix java-quarkus devfile sample. continue fi log "Validating ${DEVWORKSPACE_NAME} .." @@ -454,7 +452,7 @@ PROJEOF echo "TEST [${total_count}/${total_tests}] ${devfile_url} with ${image} PASSED ✅" ((success_count++)) else - if shouldExclude ${image}; then + if shouldExclude "${image}"; then echo "TEST [${total_count}/${total_tests}] ${devfile_url} with ${image} FAILED ❌ (EXCLUDED ↩️ )" excluded_test+=("Devfile '$devfile_url' using image '$image'") else From a3c41c3df0f8588c7ea38dd8e6fbb60d589e3a8e Mon Sep 17 00:00:00 2001 From: Stephane Bouchet Date: Fri, 14 Aug 2026 17:15:01 -0500 Subject: [PATCH 5/9] updated readme Signed-off-by: Stephane Bouchet --- README.md | 41 ++++++++++++++++++++++++++--------------- 1 file changed, 26 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 4c68762..04624f7 100644 --- a/README.md +++ b/README.md @@ -58,7 +58,7 @@ Automated validation tool for testing DevWorkspace instances on OpenShift cluste - Iterates through devfiles × images matrix - For each combination: creates DevWorkspace, waits for Running state, validates, records results 5. **Cleanup**: Deletes DevWorkspace and temporary files (skipped in debug mode) -6. **Summary Report**: Shows test counts, success/failure, elapsed time, and lists failed tests +6. **Summary Report**: Shows test counts, success/failure/excluded, elapsed time, and lists failed tests ### Command-Line Flags @@ -81,21 +81,24 @@ Each scenario in `settings/settings-.env` exports: - `PROJECT_URL`: Git repository URL (must include surrounding double quotes) - `EDITOR_DEFINITION`: URL to the editor definition YAML - `EDITOR_COMPONENT_NAME`: Component name in the editor definition that contains the editor image (used by `-i`/`-p` to replace the correct image) +- `EXCLUDED_IMAGE_PATTERNS`: Array of Posix Extended Regular Expressions for images excluded from failure counts - `LANDING_PAGE_PORT`: Port to curl inside the pod to validate the editor is running #### Scenario Validation All scenarios use the same validation method: curl `localhost:${LANDING_PAGE_PORT}` inside the pod and check for HTTP 200. -| Scenario | Timeout | Landing Page Port | -|----------|---------|-------------------| -| sshd | 60s | 3400 | -| jetbrains | 120s | 3400 | -| vscode | 90s | 3100 | +| Scenario | Timeout | Landing Page Port | Editor Component | +|----------|---------|-------------------|------------------| +| sshd | 60s | 3400 | che-code-sshd-page | +| jetbrains | 120s | 3400 | editor-injector | +| vscode | 90s | 3100 | che-code-injector | ### DevWorkspace Generation -Uses `devworkspace-template.yaml` as base, performs sed substitutions in two stages: +Uses `devworkspace-template.yaml` as base. The template uses ephemeral storage (`controller.devfile.io/storage-type: ephemeral`) to avoid PVC provisioning overhead during tests. + +Substitutions are performed in two stages: **Stage 1** - Metadata, devfile, and projects injection: ```bash @@ -121,6 +124,14 @@ The two-stage approach ensures devfile content is injected before image replacem **Editor contribution**: When using `-p` or `-i` (override image), the editor contribution switches from `uri:` to `kubernetes: name:` referencing the applied DevWorkspaceTemplate. +### DevWorkspace Lifecycle Management + +Between tests, the script handles the workspace depending on its current state: + +- **Running / Starting**: Gracefully stops by patching `spec.started: false` and waiting up to `TIMEOUT/4` seconds for `Stopped` state. +- **Failed**: Force-deletes the workspace (`oc delete dw`). A Failed workspace with CrashLoopBackOff containers can take too long to stop gracefully, which would cascade into subsequent test failures. The next `oc apply` recreates it cleanly. +- **Stopped / not found**: Proceeds directly to `oc apply`. + ### Logging and Output Control - `log()`: Outputs only when `VERBOSE=1` (set by `-v` or `-d` flags) @@ -143,18 +154,18 @@ settings/ settings-vscode.env # VSCode scenario: timeout=90s, port 3100 images/ - images.txt # Quick test list (3 UDI images: ubi8, ubi9, ubi10) - images-full.txt # Complete test matrix (UDI, base-developer-image, and UBI variants) + images.txt # Default test list (3 UDI images: ubi8, ubi9, ubi10) + images-full.txt # Complete test matrix (227 images including UDI, base-developer-image, and UBI variants) devfiles/ - devfiles.txt # Quick test list (nodejs, go, php-laravel, python) + devfiles.txt # Default test list (nodejs, go, php-laravel, python, java-quarkus) devfiles-full.txt # Complete devfile list (32 devfiles from devfile registry) samples/ samples.txt # Sample project URLs (currently unused) samples-full.txt # Extended sample project list (currently unused) -devworkspace-template.yaml # Base template with placeholders +devworkspace-template.yaml # Base template with placeholders (ephemeral storage) dw-auto-validate.sh # Main validation orchestrator verify_images.sh # Skopeo-based image accessibility checker ``` @@ -188,7 +199,7 @@ git: ```bash state="" count=0 -while [ "${state}" != "Running" ] && [ ${count} -lt ${TIMEOUT} ]; do +while [ "${state}" != "Running" ] && [ "${state}" != "Failed" ] && [ ${count} -lt ${TIMEOUT} ]; do state=$(oc get dw ${DEVWORKSPACE_NAME} -o 'jsonpath={.status.phase}') sleep 1s count=$((count+1)) @@ -197,8 +208,8 @@ done **Finding pod by DevWorkspace label**: ```bash -podNameAndDWName=$(oc get pods -o 'jsonpath={range .items[*]}{.metadata.name}{","}{.metadata.labels.controller\.devfile\.io/devworkspace_name}{end}') -podName=$(echo ${podNameAndDWName} | grep ${DEVWORKSPACE_NAME} | cut -d, -f1) +podNameAndDWName=$(oc get pods -o 'jsonpath={range .items[*]}{.metadata.name}{","}{.metadata.labels.controller\.devfile\.io/devworkspace_name}{"\n"}{end}') +podName=$(echo "${podNameAndDWName}" | grep ${DEVWORKSPACE_NAME} | cut -d, -f1) ``` **Getting main container name** (from pod status, excluding `che-*` containers): @@ -209,5 +220,5 @@ mainContainerName=$(oc get pod "${podName}" -o json | jq -r '[.status.containerS ### Adding a New Scenario 1. Create `settings/settings-.env` -2. Export required variables: `TIMEOUT`, `DEVWORKSPACE_NAME`, `PROJECT_URL`, `EDITOR_DEFINITION`, `EDITOR_COMPONENT_NAME`, `LANDING_PAGE_PORT` +2. Export required variables: `TIMEOUT`, `DEVWORKSPACE_NAME`, `PROJECT_URL`, `EDITOR_DEFINITION`, `EDITOR_COMPONENT_NAME`, `EXCLUDED_IMAGE_PATTERNS`, `LANDING_PAGE_PORT` 3. Update scenario selection in dw-auto-validate.sh (add option, update prompts and `-s` validation) From d8c0e3716a7de7bd66b99ff144e9bb0250d4332e Mon Sep 17 00:00:00 2001 From: Stephane Bouchet Date: Tue, 18 Aug 2026 13:09:06 -0500 Subject: [PATCH 6/9] Improve DevWorkspace validation reliability and shell safety - Add curl-based editor validation with -m 5 timeout (replaces port-forward in jetbrains, log-grepping in vscode, adds landing page check to sshd) - Bump vscode TIMEOUT from 60s to 120s for slow-starting devfiles - Add cleanup_test() to handle Failed/Running/Starting states between tests - Fix cleanup_suite() to suppress "not found" errors on already-deleted DW - Use ephemeral storage in devworkspace-template.yaml - Extract starterProjects from devfiles instead of hardcoding PROJECT_URL - Detect Failed state early instead of waiting full TIMEOUT - Log DevWorkspace failure reason from .status.message - Fix pod resolution: filter Running pods, use head -1, resolve container from pod status instead of devworkspace spec - Fix shell quoting throughout (QUIET, SCENARIO, read -r, array iteration) - Update samples lists, devfile lists and images lists Co-Authored-By: Claude Opus 4.6 --- README.md | 46 +++++++++---------- devfiles/devfiles.txt | 4 -- dw-auto-validate.sh | 78 ++++++++++++++++----------------- settings/settings-jetbrains.env | 18 +++++++- settings/settings-sshd.env | 33 +++++++++++++- settings/settings-vscode.env | 21 +++++++-- 6 files changed, 126 insertions(+), 74 deletions(-) diff --git a/README.md b/README.md index 04624f7..0a5ce62 100644 --- a/README.md +++ b/README.md @@ -82,17 +82,17 @@ Each scenario in `settings/settings-.env` exports: - `EDITOR_DEFINITION`: URL to the editor definition YAML - `EDITOR_COMPONENT_NAME`: Component name in the editor definition that contains the editor image (used by `-i`/`-p` to replace the correct image) - `EXCLUDED_IMAGE_PATTERNS`: Array of Posix Extended Regular Expressions for images excluded from failure counts -- `LANDING_PAGE_PORT`: Port to curl inside the pod to validate the editor is running +- `validate_devworkspace()`: Function that evaluates whether the DevWorkspace is valid (scenario-specific checks) #### Scenario Validation -All scenarios use the same validation method: curl `localhost:${LANDING_PAGE_PORT}` inside the pod and check for HTTP 200. +Each scenario defines its own `validate_devworkspace()` function with checks tailored to its editor: -| Scenario | Timeout | Landing Page Port | Editor Component | +| Scenario | Timeout | Validation Checks | Editor Component | |----------|---------|-------------------|------------------| -| sshd | 60s | 3400 | che-code-sshd-page | -| jetbrains | 120s | 3400 | editor-injector | -| vscode | 90s | 3100 | che-code-injector | +| sshd | 60s | HTTP check on port 3400 via `oc exec` curl (`-m 5`) + `/tmp/sshd.log` for `Server listening on` | che-code-sshd-page | +| jetbrains | 120s | HTTP check on port 3400 via `oc exec` curl (`-m 5`) | editor-injector | +| vscode | 120s | HTTP check on port 3100 via `oc exec` curl (`-m 5`), dumps `/checode/entrypoint-logs.txt` on failure | che-code-injector | ### DevWorkspace Generation @@ -126,11 +126,16 @@ The two-stage approach ensures devfile content is injected before image replacem ### DevWorkspace Lifecycle Management -Between tests, the script handles the workspace depending on its current state: +After each test, `cleanup_test()` handles the workspace depending on its current state: - **Running / Starting**: Gracefully stops by patching `spec.started: false` and waiting up to `TIMEOUT/4` seconds for `Stopped` state. -- **Failed**: Force-deletes the workspace (`oc delete dw`). A Failed workspace with CrashLoopBackOff containers can take too long to stop gracefully, which would cascade into subsequent test failures. The next `oc apply` recreates it cleanly. -- **Stopped / not found**: Proceeds directly to `oc apply`. +- **Failed**: Force-deletes the workspace (`oc delete dw`) and waits for all associated pods to terminate before returning. This prevents stale pods from interfering with the next test's pod resolution. +- **Stopped / not found**: No action needed. +- **Debug mode**: `cleanup_test()` is skipped entirely to allow resource inspection. + +At the end of the suite, `cleanup_suite()` deletes the DevWorkspace, any override DevWorkspaceTemplate, and temporary files (skipped in debug mode). + +When a DevWorkspace fails to start, the script logs the failure reason from `.status.message`. ### Logging and Output Control @@ -154,12 +159,12 @@ settings/ settings-vscode.env # VSCode scenario: timeout=90s, port 3100 images/ - images.txt # Default test list (3 UDI images: ubi8, ubi9, ubi10) + images.txt # Default test list (UDI images: ubi8, ubi9, ubi10) images-full.txt # Complete test matrix (227 images including UDI, base-developer-image, and UBI variants) devfiles/ - devfiles.txt # Default test list (nodejs, go, php-laravel, python, java-quarkus) - devfiles-full.txt # Complete devfile list (32 devfiles from devfile registry) + devfiles.txt # Default test list (nodejs) + devfiles-full.txt # Complete devfile list (32 devfiles from devfile registry including java-quarkus, ollama, openclaw, picoclaw, zeroclaw) samples/ samples.txt # Sample project URLs (currently unused) @@ -174,13 +179,9 @@ verify_images.sh # Skopeo-based image accessibility checker ### Validation Function -A single `validate_devworkspace()` function in the main script handles all scenarios: - -1. Calls `resolve_devworkspace_pod()` to set `podName` and `mainContainerName` globals -2. Curls `localhost:${LANDING_PAGE_PORT}` inside the pod container -3. Returns 0 if HTTP 200, 1 otherwise +Each scenario defines its own `validate_devworkspace()` function in its settings file. All scenarios call `resolve_devworkspace_pod()` to set `podName` and `mainContainerName` globals, then perform scenario-specific checks (landing page HTTP check, log file scraping, or both). -`resolve_devworkspace_pod()` finds the pod by DevWorkspace label and selects the main container from pod status, filtering out containers whose name starts with `che-`. +`resolve_devworkspace_pod()` finds the pod by DevWorkspace label (filtering for `status.phase=Running` to exclude terminating pods) and selects the main container from pod status, filtering out containers whose name starts with `che-`. ### Variable Quoting Requirements @@ -206,10 +207,10 @@ while [ "${state}" != "Running" ] && [ "${state}" != "Failed" ] && [ ${count} -l done ``` -**Finding pod by DevWorkspace label**: +**Finding pod by DevWorkspace label** (filtered to Running pods only): ```bash -podNameAndDWName=$(oc get pods -o 'jsonpath={range .items[*]}{.metadata.name}{","}{.metadata.labels.controller\.devfile\.io/devworkspace_name}{"\n"}{end}') -podName=$(echo "${podNameAndDWName}" | grep ${DEVWORKSPACE_NAME} | cut -d, -f1) +podNameAndDWName=$(oc get pods --field-selector=status.phase=Running -o 'jsonpath={range .items[*]}{.metadata.name}{","}{.metadata.labels.controller\.devfile\.io/devworkspace_name}{"\n"}{end}') +podName=$(echo "${podNameAndDWName}" | grep ${DEVWORKSPACE_NAME} | head -1 | cut -d, -f1) ``` **Getting main container name** (from pod status, excluding `che-*` containers): @@ -220,5 +221,6 @@ mainContainerName=$(oc get pod "${podName}" -o json | jq -r '[.status.containerS ### Adding a New Scenario 1. Create `settings/settings-.env` -2. Export required variables: `TIMEOUT`, `DEVWORKSPACE_NAME`, `PROJECT_URL`, `EDITOR_DEFINITION`, `EDITOR_COMPONENT_NAME`, `EXCLUDED_IMAGE_PATTERNS`, `LANDING_PAGE_PORT` +2. Export required variables: `TIMEOUT`, `DEVWORKSPACE_NAME`, `PROJECT_URL`, `EDITOR_DEFINITION`, `EDITOR_COMPONENT_NAME`, `EXCLUDED_IMAGE_PATTERNS` +3. Define a `validate_devworkspace()` function with scenario-specific validation checks 3. Update scenario selection in dw-auto-validate.sh (add option, update prompts and `-s` validation) diff --git a/devfiles/devfiles.txt b/devfiles/devfiles.txt index 863eeee..b87138d 100644 --- a/devfiles/devfiles.txt +++ b/devfiles/devfiles.txt @@ -1,5 +1 @@ https://registry.devfile.io/devfiles/nodejs -https://registry.devfile.io/devfiles/go -https://registry.devfile.io/devfiles/php-laravel -https://registry.devfile.io/devfiles/python -https://registry.devfile.io/devfiles/java-quarkus diff --git a/dw-auto-validate.sh b/dw-auto-validate.sh index dea7a8e..3055983 100755 --- a/dw-auto-validate.sh +++ b/dw-auto-validate.sh @@ -96,9 +96,9 @@ log() { # Sets global variables: podName, mainContainerName # Returns 1 if pod or container cannot be found. resolve_devworkspace_pod() { - podNameAndDWName=$(oc get pods -o 'jsonpath={range .items[*]}{.metadata.name}{","}{.metadata.labels.controller\.devfile\.io/devworkspace_name}{"\n"}{end}') + podNameAndDWName=$(oc get pods --field-selector=status.phase=Running -o 'jsonpath={range .items[*]}{.metadata.name}{","}{.metadata.labels.controller\.devfile\.io/devworkspace_name}{"\n"}{end}') log "${YELLOW}podNameAndDWName: \n${NC}${podNameAndDWName}" - podName=$(echo "${podNameAndDWName}" | grep ${DEVWORKSPACE_NAME} | cut -d, -f1) + podName=$(echo "${podNameAndDWName}" | grep ${DEVWORKSPACE_NAME} | head -1 | cut -d, -f1) log "${YELLOW}podName: \n${NC}${podName}" mainContainerName=$(oc get pod "${podName}" -o json | jq -r '[.status.containerStatuses[] | select(.state.running and (.name | test("^che-") | not))] | first | .name // empty') log "${YELLOW}mainContainerName: \n${NC}${mainContainerName}" @@ -110,17 +110,33 @@ resolve_devworkspace_pod() { return 0 } -validate_devworkspace() { - resolve_devworkspace_pod || return 1 - - log "Checking editor on localhost:${LANDING_PAGE_PORT}" - http_code=$(oc exec -n "${DEVWORKSPACE_NS}" "${podName}" -c "${mainContainerName}" -- curl -s -o /dev/null -w '%{http_code}' http://localhost:${LANDING_PAGE_PORT}) - if [ "${http_code}" == "200" ]; then - log "${GREEN}localhost:${VALIDATION_PORT} returned HTTP ${http_code}${NC}" - return 0 - else - log "${YELLOW}localhost:${VALIDATION_PORT} returned HTTP ${http_code}${NC}" - return 1 +# Stops or deletes the current DevWorkspace to prepare for the next test. +cleanup_test() { + [ ${DEBUG} -eq 1 ] && log "\n${YELLOW}Debug mode:${NC} Skipping test cleanup, DevWorkspace (${DEVWORKSPACE_NAME}) not deleted." && return 0 + current_phase=$(oc get dw ${DEVWORKSPACE_NAME} -o 'jsonpath={.status.phase}' 2>/dev/null) + if [ "${current_phase}" == "Failed" ]; then + log -n "Force-deleting ${DEVWORKSPACE_NAME} (Failed state) " + oc delete dw ${DEVWORKSPACE_NAME} &>/dev/null + # wait for pods to fully terminate before next test + while oc get pods -l "controller.devfile.io/devworkspace_name=${DEVWORKSPACE_NAME}" --no-headers 2>/dev/null | grep -q .; do + sleep 1s + log -n "." + done + log " deleted." + elif [ "${current_phase}" == "Running" ] || [ "${current_phase}" == "Starting" ]; then + eval "oc patch dw ${DEVWORKSPACE_NAME} --type merge -p '{\"spec\":{\"started\":false}}' ${QUIET}" + log -n "Stopping ${DEVWORKSPACE_NAME} ." + stop_count=0 + stop_timeout=$((TIMEOUT / 4)) + while [ "$(oc get dw ${DEVWORKSPACE_NAME} -o 'jsonpath={.status.phase}' 2>/dev/null)" != "Stopped" ] && [ ${stop_count} -lt ${stop_timeout} ]; do + sleep 1s + log -n "." + stop_count=$((stop_count+1)) + done + if [ ${stop_count} -ge ${stop_timeout} ]; then + log "\n${YELLOW}${DEVWORKSPACE_NAME} failed to stop (timed out after ${stop_timeout}s)${NC}" + fi + log " stopped." fi } @@ -395,30 +411,6 @@ PROJEOF # Modify the result (must be separate) # here is the replacement of the container image used in the devfile from an image in the list eval "sed \"s|image: .*|image: ${image}|\" > ${TMP_DEVWORKSPACE}" - # Stop the DevWorkspace before applying to force a pod restart - current_phase=$(oc get dw ${DEVWORKSPACE_NAME} -o 'jsonpath={.status.phase}' 2>/dev/null) - if [ "${current_phase}" == "Failed" ]; then - log -n "Force-deleting ${DEVWORKSPACE_NAME} (Failed state) ." - eval "oc delete dw ${DEVWORKSPACE_NAME} ${QUIET}" - log " deleted." - elif [ "${current_phase}" == "Running" ] || [ "${current_phase}" == "Starting" ]; then - eval "oc patch dw ${DEVWORKSPACE_NAME} --type merge -p '{\"spec\":{\"started\":false}}' ${QUIET}" - log -n "Stopping ${DEVWORKSPACE_NAME} ." - stop_count=0 - stop_timeout=$((TIMEOUT / 4)) - while [ "$(oc get dw ${DEVWORKSPACE_NAME} -o 'jsonpath={.status.phase}' 2>/dev/null)" != "Stopped" ] && [ ${stop_count} -lt ${stop_timeout} ]; do - sleep 1s - log -n "." - stop_count=$((stop_count+1)) - done - if [ ${stop_count} -ge ${stop_timeout} ]; then - log "\n${YELLOW}${DEVWORKSPACE_NAME} failed to stop (timed out after ${stop_timeout}s)${NC}" - echo "TEST [${total_count}/${total_tests}] ${devfile_url} with ${image} FAILED ❌" - failed_test+=("Devfile '$devfile_url' using image '$image'") - continue - fi - log " stopped." - fi eval "oc apply -f ${TMP_DEVWORKSPACE} ${QUIET}" state="" log -n "Waiting for ${DEVWORKSPACE_NAME} to run ." @@ -433,7 +425,9 @@ PROJEOF log "\n${GREEN}${DEVWORKSPACE_NAME} is running.${NC}" else if [ "${state}" == "Failed" ]; then + dw_message=$(oc get dw ${DEVWORKSPACE_NAME} -o 'jsonpath={.status.message}' 2>/dev/null) log "\n${YELLOW}${DEVWORKSPACE_NAME} failed to start (state: Failed after ${count}s)${NC}" + log "${RED}Reason: ${dw_message}${NC}" else log "\n${YELLOW}${DEVWORKSPACE_NAME} failed to start (timed out after ${TIMEOUT}s, last state: ${state})${NC}" fi @@ -444,6 +438,7 @@ PROJEOF echo "TEST [${total_count}/${total_tests}] ${devfile_url} with ${image} FAILED ❌" failed_test+=("Devfile '$devfile_url' using image '$image'") fi + cleanup_test continue fi log "Validating ${DEVWORKSPACE_NAME} .." @@ -460,15 +455,16 @@ PROJEOF failed_test+=("Devfile '$devfile_url' using image '$image'") fi fi + cleanup_test sleep 1s done # image loop [[ ${DEBUG} -eq 1 && ${total_count} -ge 1 ]] && break done # devfile loop -# cleanup -cleanup() { +# cleanup suite: delete remote resources and temporary files +cleanup_suite() { echo -e "\n${BLUE}Cleaning up resources...${NC}" - eval "oc delete dw ${DEVWORKSPACE_NAME} ${QUIET}" + eval "oc delete dw ${DEVWORKSPACE_NAME} 2>/dev/null ${QUIET}" if [ -n "${OVERRIDE_IMAGE}" ]; then eval "oc delete devworkspacetemplate ${EDITOR_DWT_NAME} ${QUIET}" fi @@ -484,7 +480,7 @@ cleanup() { } if [ ${DEBUG} -eq 0 ]; then - cleanup + cleanup_suite else EXTRA_MSG="" [ -n "${OVERRIDE_IMAGE}" ] && EXTRA_MSG="\nTemporary editor definition file (${TMP_EDITOR_DEF}) not deleted\nTemporary devworkspace template file (${TMP_DWT}) not deleted\nRemote DevworkspaceTemplate (${EDITOR_DWT_NAME}) not deleted" diff --git a/settings/settings-jetbrains.env b/settings/settings-jetbrains.env index 5b7181a..983e080 100644 --- a/settings/settings-jetbrains.env +++ b/settings/settings-jetbrains.env @@ -18,5 +18,19 @@ export EDITOR_COMPONENT_NAME='editor-injector' # array of image patterns (Posix Extended Regular Expressions) excluded from failed tests export EXCLUDED_IMAGE_PATTERNS=('ubi[0-9]{0,2}-micro' 'ubi[0-9]{0,2}-stig' 'ubi[0-9]{0,2}/pause') -# port to check for editor readiness -export LANDING_PAGE_PORT=3400 +# Function that evaluates whether DevWorkspace is valid +validate_devworkspace() { + LANDING_PAGE_PORT=3400 + + resolve_devworkspace_pod || return 1 + + log "Checking jetbrains landing page on localhost:${LANDING_PAGE_PORT}" + http_code=$(oc exec -n "${DEVWORKSPACE_NS}" "${podName}" -c "${mainContainerName}" -- curl -s -m 5 -o /dev/null -w '%{http_code}' http://localhost:${LANDING_PAGE_PORT}) + if [ "${http_code}" == "200" ]; then + log "${GREEN}localhost:${LANDING_PAGE_PORT} returned HTTP code ${http_code}${NC}" + return 0 + else + log "${YELLOW}localhost:${LANDING_PAGE_PORT} returned HTTP code ${http_code}${NC}" + return 1 + fi +} \ No newline at end of file diff --git a/settings/settings-sshd.env b/settings/settings-sshd.env index d27c69d..19ff1a4 100644 --- a/settings/settings-sshd.env +++ b/settings/settings-sshd.env @@ -18,5 +18,34 @@ export EDITOR_COMPONENT_NAME='che-code-sshd-page' # array of image patterns (Posix Extended Regular Expressions) excluded from failed tests export EXCLUDED_IMAGE_PATTERNS=('ubi[0-9]{0,2}-micro' 'ubi[0-9]{0,2}-stig' 'ubi[0-9]{0,2}/pause') -# port to check for editor readiness -export LANDING_PAGE_PORT=3400 +# Function that evaluates whether DevWorkspace is valid +validate_devworkspace() { + + LANDING_PAGE_PORT=3400 + + resolve_devworkspace_pod || return 1 + + log "Checking landing page readiness on localhost:${LANDING_PAGE_PORT}" + http_code=$(oc exec -n "${DEVWORKSPACE_NS}" "${podName}" -c "${mainContainerName}" -- curl -s -m 5 -o /dev/null -w '%{http_code}' http://localhost:${LANDING_PAGE_PORT}) + if [ "${http_code}" != "200" ]; then + log "localhost:${LANDING_PAGE_PORT} returned HTTP code ${http_code}" + return 1 + fi + + log "Checking /tmp/sshd.log file contents" + oc exec -n ${DEVWORKSPACE_NS} ${podName} -c ${mainContainerName} -- cat /tmp/sshd.log &>/dev/null #always quiet this message an rely on return code + if [ $? -eq 1 ]; then + # fail to cat ssh log file + log "Failed to cat /tmp/sshd.log file on container" + return 1 + fi + res=$(oc exec -n ${DEVWORKSPACE_NS} ${podName} -c ${mainContainerName} -- cat /tmp/sshd.log | grep -q 'Server listening on') + if [ $? -eq 0 ]; then + # pass + return 0 + else + # fail + log "Failed to retrieve sucessfull sshd status log in /tmp/sshd.log file on container" + return 1 + fi +} diff --git a/settings/settings-vscode.env b/settings/settings-vscode.env index 73e2507..28c140a 100644 --- a/settings/settings-vscode.env +++ b/settings/settings-vscode.env @@ -1,7 +1,7 @@ #! /bin/bash # Rough time (seconds) to wait for DevWorkspace to enter 'Running' state -export TIMEOUT=90 +export TIMEOUT=120 # name to give all created (singleton) DevWorkspace instances export DEVWORKSPACE_NAME='vscode-test' @@ -18,5 +18,20 @@ export EDITOR_COMPONENT_NAME='che-code-injector' # array of image patterns (Posix Extended Regular Expressions) excluded from failed tests export EXCLUDED_IMAGE_PATTERNS=('ubi[0-9]{0,2}-micro' 'ubi[0-9]{0,2}-stig' 'ubi[0-9]{0,2}/pause') -# port to check for editor readiness -export LANDING_PAGE_PORT=3100 +# Function that evaluates whether DevWorkspace is valid +validate_devworkspace() { + + LANDING_PAGE_PORT=3100 + + resolve_devworkspace_pod || return 1 + + log "Checking editor on localhost:${LANDING_PAGE_PORT}" + http_code=$(oc exec -n "${DEVWORKSPACE_NS}" "${podName}" -c "${mainContainerName}" -- curl -s -m 5 -o /dev/null -w '%{http_code}' http://localhost:${LANDING_PAGE_PORT}) + if [ "${http_code}" == "200" ]; then + return 0 + fi + + log "localhost:${LANDING_PAGE_PORT} returned HTTP code ${http_code}, checking entrypoint logs..." + log $(oc exec -n ${DEVWORKSPACE_NS} ${podName} -c ${mainContainerName} -- cat /checode/entrypoint-logs.txt 2>/dev/null) + return 1 +} From 58b56500092855959e199dfdb920efb5986d23bf Mon Sep 17 00:00:00 2001 From: Stephane Bouchet Date: Thu, 20 Aug 2026 16:45:34 -0500 Subject: [PATCH 7/9] replaced java-quarkus by java-maven Signed-off-by: Stephane Bouchet --- devfiles/devfiles.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/devfiles/devfiles.txt b/devfiles/devfiles.txt index b87138d..5488c06 100644 --- a/devfiles/devfiles.txt +++ b/devfiles/devfiles.txt @@ -1 +1,5 @@ https://registry.devfile.io/devfiles/nodejs +https://registry.devfile.io/devfiles/go +https://registry.devfile.io/devfiles/php-laravel +https://registry.devfile.io/devfiles/python +https://registry.devfile.io/devfiles/java-maven From 1d8ea60abf6490ab633d6f2340d001f1c8f5d955 Mon Sep 17 00:00:00 2001 From: Stephane Bouchet Date: Thu, 20 Aug 2026 17:06:57 -0500 Subject: [PATCH 8/9] removed all extraDevfiles that kinda can't work as-is (from https://github.com/devfile/registry/blob/main/extraDevfileEntries.yaml) Signed-off-by: Stephane Bouchet --- devfiles/devfiles-full.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/devfiles/devfiles-full.txt b/devfiles/devfiles-full.txt index 98f185d..138a071 100644 --- a/devfiles/devfiles-full.txt +++ b/devfiles/devfiles-full.txt @@ -1,6 +1,5 @@ https://registry.devfile.io/devfiles/dotnet80 https://registry.devfile.io/devfiles/go -https://registry.devfile.io/devfiles/code-with-quarkus https://registry.devfile.io/devfiles/java-maven https://registry.devfile.io/devfiles/java-openliberty https://registry.devfile.io/devfiles/java-openliberty-gradle From 2026495808b3181bd22490c7641612841e65ab19 Mon Sep 17 00:00:00 2001 From: Stephane Bouchet Date: Thu, 20 Aug 2026 17:17:38 -0500 Subject: [PATCH 9/9] updated readme Signed-off-by: Stephane Bouchet --- README.md | 28 ++++++++++++++++++---------- devfiles/devfiles-full.txt | 1 - 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 0a5ce62..771a654 100644 --- a/README.md +++ b/README.md @@ -70,7 +70,7 @@ Automated validation tool for testing DevWorkspace instances on OpenShift cluste - `-i `: Test a custom editor image — same mechanism as `-p` but with an arbitrary image reference (mutually exclusive with `-p`) - `-h`: Help - displays usage information -**Debug mode specifics**: Sets `DEBUG=1`, `FULL=0`, `VERBOSE=1`, runs only the first test iteration (`[[ ${DEBUG} -eq 1 && ${total_count} == 1 ]] && continue`), skips cleanup to allow resource inspection. +**Debug mode specifics**: Sets `DEBUG=1`, `FULL=0`, `VERBOSE=1`, runs only the first test iteration (`[[ ${DEBUG} -eq 1 && ${total_count} -ge 1 ]] && break`), skips cleanup to allow resource inspection. ### Scenarios @@ -90,9 +90,11 @@ Each scenario defines its own `validate_devworkspace()` function with checks tai | Scenario | Timeout | Validation Checks | Editor Component | |----------|---------|-------------------|------------------| -| sshd | 60s | HTTP check on port 3400 via `oc exec` curl (`-m 5`) + `/tmp/sshd.log` for `Server listening on` | che-code-sshd-page | -| jetbrains | 120s | HTTP check on port 3400 via `oc exec` curl (`-m 5`) | editor-injector | -| vscode | 120s | HTTP check on port 3100 via `oc exec` curl (`-m 5`), dumps `/checode/entrypoint-logs.txt` on failure | che-code-injector | +| sshd | 60s | HTTP 200 on port 3400 via `oc exec` curl (`-m 5`), then `/tmp/sshd.log` for `Server listening on`; on failure dumps `/proc/net/tcp{,6}` via `proc_tcp` for socket diagnostics | che-code-sshd-page | +| jetbrains | 120s | HTTP 200 on port 3400 via `oc exec` curl (`-m 5`) | editor-injector | +| vscode | 120s | HTTP 200 on port 3100 via `oc exec` curl (`-m 5`), dumps `/checode/entrypoint-logs.txt` on failure | che-code-injector | + +All scenarios use `oc exec` with in-pod `curl` for validation (no port-forwarding). `validate_devworkspace()` takes no arguments — it uses global variables set by the main loop. ### DevWorkspace Generation @@ -120,6 +122,8 @@ eval "sed \"s|image: .*|image: ${image}|\" > ${TMP_DEVWORKSPACE}" The two-stage approach ensures devfile content is injected before image replacement. +**Devfile fetch**: Each devfile URL is fetched with `curl` and the HTTP status code is checked. If the fetch fails (non-200), the devfile is skipped and recorded as a failure. + **Projects handling**: If the devfile contains `starterProjects`, those are extracted and converted into a `projects:` block. Otherwise, the scenario's `PROJECT_URL` is used as a fallback sample project. **Editor contribution**: When using `-p` or `-i` (override image), the editor contribution switches from `uri:` to `kubernetes: name:` referencing the applied DevWorkspaceTemplate. @@ -133,9 +137,11 @@ After each test, `cleanup_test()` handles the workspace depending on its current - **Stopped / not found**: No action needed. - **Debug mode**: `cleanup_test()` is skipped entirely to allow resource inspection. +`cleanup_test()` runs after every test iteration (both pass and fail paths), not just at the end of the suite. + At the end of the suite, `cleanup_suite()` deletes the DevWorkspace, any override DevWorkspaceTemplate, and temporary files (skipped in debug mode). -When a DevWorkspace fails to start, the script logs the failure reason from `.status.message`. +The wait loop now detects both `Running` and `Failed` states, breaking early on failure instead of waiting for the full timeout. When a DevWorkspace fails to start, the script logs the failure reason from `.status.message`. ### Logging and Output Control @@ -156,15 +162,15 @@ Tracks test execution time using bash's `$SECONDS` variable: settings/ settings-sshd.env # SSHD scenario: timeout=60s, port 3400 settings-jetbrains.env # JetBrains scenario: timeout=120s, port 3400 - settings-vscode.env # VSCode scenario: timeout=90s, port 3100 + settings-vscode.env # VSCode scenario: timeout=120s, port 3100 images/ images.txt # Default test list (UDI images: ubi8, ubi9, ubi10) - images-full.txt # Complete test matrix (227 images including UDI, base-developer-image, and UBI variants) + images-full.txt # Complete test matrix (227 images including UDI, base-developer-image, and UBI 8/9/10 variants) devfiles/ - devfiles.txt # Default test list (nodejs) - devfiles-full.txt # Complete devfile list (32 devfiles from devfile registry including java-quarkus, ollama, openclaw, picoclaw, zeroclaw) + devfiles.txt # Default test list (nodejs, go, php-laravel, python, java-maven) + devfiles-full.txt # Complete devfile list (30 devfiles from devfile registry including java-maven, ollama, openclaw, picoclaw, zeroclaw) samples/ samples.txt # Sample project URLs (currently unused) @@ -196,7 +202,7 @@ git: ### Common DevWorkspace Patterns -**Waiting for Running state**: +**Waiting for Running or Failed state**: ```bash state="" count=0 @@ -207,6 +213,8 @@ while [ "${state}" != "Running" ] && [ "${state}" != "Failed" ] && [ ${count} -l done ``` +The loop exits early on `Failed` state, avoiding unnecessary waits for workspaces that will never start. + **Finding pod by DevWorkspace label** (filtered to Running pods only): ```bash podNameAndDWName=$(oc get pods --field-selector=status.phase=Running -o 'jsonpath={range .items[*]}{.metadata.name}{","}{.metadata.labels.controller\.devfile\.io/devworkspace_name}{"\n"}{end}') diff --git a/devfiles/devfiles-full.txt b/devfiles/devfiles-full.txt index 138a071..a1253b4 100644 --- a/devfiles/devfiles-full.txt +++ b/devfiles/devfiles-full.txt @@ -3,7 +3,6 @@ https://registry.devfile.io/devfiles/go https://registry.devfile.io/devfiles/java-maven https://registry.devfile.io/devfiles/java-openliberty https://registry.devfile.io/devfiles/java-openliberty-gradle -https://registry.devfile.io/devfiles/java-quarkus https://registry.devfile.io/devfiles/java-springboot https://registry.devfile.io/devfiles/java-vertx https://registry.devfile.io/devfiles/java-websphereliberty