diff --git a/.github/workflows/validate-with-registry.yaml b/.github/workflows/validate-with-registry.yaml new file mode 100644 index 0000000000..b598f4a49f --- /dev/null +++ b/.github/workflows/validate-with-registry.yaml @@ -0,0 +1,87 @@ +name: Validate with Devfile Registry Tests + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + workflow_dispatch: + +jobs: + validate-devfile: + runs-on: ubuntu-latest + + env: + REGISTRY_PATH: ${{ github.workspace }}/registry + + steps: + - name: Checkout current repo + uses: actions/checkout@v4 + with: + path: current-repo + + - name: Checkout devfile registry + uses: actions/checkout@v4 + with: + repository: devfile/registry + path: registry + + - name: Setup test environment with overrides + run: | + # Copy entire project for devfile validation + mkdir -p test-sample + cp -r current-repo/* test-sample/ + + # Stage registry test files and apply local overrides + mkdir -p test-environment/tests + cp -r registry/tests/* test-environment/tests/ + + if [ -d "current-repo/tests" ]; then + echo "Applying local test overrides..." + cp -r current-repo/tests/* test-environment/tests/ 2>/dev/null || true + fi + + # Copy staged files to execution location + cp -r test-environment/tests/* registry/tests/ + cp -r test-sample/* registry/tests/ + + - name: Setup Go + uses: actions/setup-go@v4 + with: + go-version: '1.21' + + - name: Install dependencies + run: | + sudo wget -qO /usr/local/bin/yq https://github.com/mikefarah/yq/releases/latest/download/yq_linux_amd64 + sudo chmod +x /usr/local/bin/yq + go install github.com/onsi/ginkgo/v2/ginkgo@latest + + - name: Setup Minikube + uses: medyagh/setup-minikube@master + with: + minikube-version: latest + kubernetes-version: v1.28.0 + + - name: Run Registry Validation Tests + env: + REGISTRY_PATH: ${{ github.workspace }}/registry + DEVFILE_PATH: ${{ github.workspace }}/registry/tests/devfile.yaml + TEST_NAMESPACE: "default" + YQ_PATH: "yq" + ENV: "minikube" + run: | + cd registry/tests/check_non_terminating + go build -o flatten-parent . + cd ../../.. + + echo "=== Schema Validation ===" + cd registry/tests + bash validate_devfile_schemas.sh + + echo "=== Non-terminating Test ===" + bash check_non_terminating.sh + + - name: Cleanup + if: always() + run: | + kubectl delete pods --all -n default --ignore-not-found=true || true \ No newline at end of file diff --git a/renovate.json b/renovate.json new file mode 100644 index 0000000000..4c75b63178 --- /dev/null +++ b/renovate.json @@ -0,0 +1,18 @@ +{ + "$schema": "https://docs.renovatebot.com/renovate-schema.json", + "enabledManagers": ["pip_requirements"], + "pip_requirements": { + "fileMatch": ["requirements\\.txt$"] + }, + "packageRules": [ + { + "matchManagers": ["pip_requirements"], + "groupName": "python dependencies", + "groupSlug": "python-deps", + "commitMessageTopic": "Python {{depName}}" + } + ], + "vulnerabilityAlerts": { + "enabled": true + } +} \ No newline at end of file diff --git a/tests/README.md b/tests/README.md new file mode 100644 index 0000000000..a9fc06d488 --- /dev/null +++ b/tests/README.md @@ -0,0 +1,5 @@ +# Test Scripts + +These test scripts are adapted from the [devfile/registry](https://github.com/devfile/registry) repository. + +Our CI workflow clones the registry repository and uses their test infrastructure, with the ability to override any test scripts by placing them in this directory. \ No newline at end of file diff --git a/tests/check_non_terminating.sh b/tests/check_non_terminating.sh new file mode 100644 index 0000000000..161c92058b --- /dev/null +++ b/tests/check_non_terminating.sh @@ -0,0 +1,211 @@ +#!/bin/bash +set -o nounset +set -o errexit + +DEVFILE_PATH=${DEVFILE_PATH:-"$(pwd)/devfile.yaml"} + +REGISTRY_PATH=${REGISTRY_PATH:-"../registry"} + +BIN_NAME=${BIN_NAME:-"flatten-parent"} +NON_TERMINATING_MODULE_BIN="${REGISTRY_PATH}/tests/check_non_terminating/$BIN_NAME" + +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=240 # <== includes image pulling + + # workaround: cri-dockerd v0.2.6+ fixes a timeout issue where large images are not being pulled + # this can be removed when actions-setup-minikube updates cri-dockerd + if [ "$ENV" = "minikube" ]; then + echo " COMMAND: minikube ssh docker pull $_int_image" + minikube ssh docker pull $_int_image >/dev/null 2>&1 + fi + + 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} + +if [ -z "${ENV:-}" ]; then + ENV=minikube +fi + +if [ "$ENV" != "minikube" ] && [ "$ENV" != "openshift" ]; then + echo "ERROR:: Allowed values for ENV are either \"minikube\" (default) or \"openshift\"." + exit 1 +fi + +if [ ! -f "$NON_TERMINATING_MODULE_BIN" ]; then + echo "ERROR: Go binary not found at $NON_TERMINATING_MODULE_BIN" + echo "Please ensure the devfile/registry repository is cloned and the binary is built." + exit 1 +fi + +if [ ! -f "$DEVFILE_PATH" ]; then + echo "ERROR: Devfile not found at path $DEVFILE_PATH" + exit 1 +fi + +echo "=======================" +echo "Testing single sample: ${DEVFILE_PATH}" + +# if devfile in path has a parent flatten it +if $YQ_PATH eval 'has("parent")' "$DEVFILE_PATH" -r | grep -q "true"; then + echo "INFO:: Found parent for $DEVFILE_PATH" + "$NON_TERMINATING_MODULE_BIN" "$DEVFILE_PATH" +fi + +IFS=" " read -r -a 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 "=======================" + exit 0 +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 "=======================" + exit 0 +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 + +echo "Sample: $name" +echo "Image: $image" +echo "Command: $command_string" +echo "Args: $command_args_string" + +isNonTerminating "${image}" "${command_string}" "${command_args_string}" + +# remove image to save space +if [ "$ENV" = "minikube" ]; then + echo " COMMAND: \"minikube ssh -- docker image rm ${image} --force\"" + minikube ssh -- docker image rm ${image} --force >/dev/null 2>&1 +fi + +echo "=======================" +echo "Non-terminating test completed successfully!" + +exit 0 \ No newline at end of file diff --git a/tests/check_odov3.sh b/tests/check_odov3.sh new file mode 100644 index 0000000000..e14dcd3bef --- /dev/null +++ b/tests/check_odov3.sh @@ -0,0 +1,38 @@ +#!/usr/bin/env bash + +set -x + +SAMPLE_PATH="$(pwd)" +DEVFILE_PATH=${DEVFILE_PATH:-"$SAMPLE_PATH/devfile.yaml"} + +REGISTRY_PATH=${REGISTRY_PATH:-"../registry"} + +args="" + +if [ ! -z "${1}" ]; then + args="-odoPath ${1} ${args}" +fi + +if [ ! -f "$DEVFILE_PATH" ]; then + echo "ERROR: Devfile not found at path $DEVFILE_PATH" + exit 1 +fi + +if [ ! -d "$REGISTRY_PATH/tests/odov3" ]; then + echo "ERROR: Registry test directory not found at $REGISTRY_PATH/tests/odov3" + echo "Please ensure the devfile/registry repository is cloned." + exit 1 +fi + +SAMPLE_NAME=$(yq eval '.metadata.name' "$DEVFILE_PATH") + +cd "$REGISTRY_PATH/tests/odov3" + +ginkgo run --procs 1 \ + --timeout 3h \ + --slow-spec-threshold 120s \ + . -- -stacksPath "$SAMPLE_PATH" -stackDirs "." ${args} + +echo "=======================" +echo "ODO v3 test completed!" +echo "=======================" \ No newline at end of file diff --git a/tests/validate_devfile_schemas.sh b/tests/validate_devfile_schemas.sh new file mode 100644 index 0000000000..8b862245d3 --- /dev/null +++ b/tests/validate_devfile_schemas.sh @@ -0,0 +1,67 @@ +#!/usr/bin/env bash + +set -x + +# Path to the devfile in this sample repository +SAMPLE_PATH="$(pwd)" +DEVFILE_PATH=${DEVFILE_PATH:-"$SAMPLE_PATH/devfile.yaml"} + + +# Path to the devfile/registry repository (assumed to be cloned as a sibling) +REGISTRY_PATH=${REGISTRY_PATH:-"../registry"} + +POSITIONAL_ARGS=() +SAMPLES="false" +VERBOSE="false" + +while [[ $# -gt 0 ]]; do + case $1 in + -s|--samples) + SAMPLES="true" + shift # past argument + ;; + -v|--verbose) + VERBOSE="true" + shift # past argument + ;; + -*|--*) + echo "Unknown option $1" + exit 1 + ;; + *) + POSITIONAL_ARGS+=("$1") # save positional arg + shift # past argument + ;; + esac +done + +set -- "${POSITIONAL_ARGS[@]}" # restore positional parameters + +# Check if devfile exists +if [ ! -f "$DEVFILE_PATH" ]; then + echo "ERROR: Devfile not found at path $DEVFILE_PATH" + exit 1 +fi + +# Check if the devfile/registry test directory exists +if [ ! -d "$REGISTRY_PATH/tests/validate_devfile_schemas" ]; then + echo "ERROR: Registry test directory not found at $REGISTRY_PATH/tests/validate_devfile_schemas" + echo "Please ensure the devfile/registry repository is cloned." + exit 1 +fi + +echo "=======================" +echo "Validating devfile schema for single sample: ${DEVFILE_PATH}" +echo "Registry path: ${REGISTRY_PATH}" +echo "=======================" + +# Change to the registry test directory and run the validation +cd "$REGISTRY_PATH/tests/validate_devfile_schemas" + +# Run the validation test with the single sample +ginkgo run --procs 1 \ + . -- -stacksPath "$SAMPLE_PATH" -stackDirs "." + +echo "=======================" +echo "Schema validation completed!" +echo "=======================" \ No newline at end of file